arout2026 commited on
Commit
af3b3c9
·
verified ·
1 Parent(s): 1a3327f

Upload 16 files

Browse files
Files changed (16) hide show
  1. .gitattributes +35 -35
  2. .gitignore +0 -0
  3. Procfile +0 -0
  4. README.md +117 -12
  5. ai_models.py +838 -0
  6. anomaly_detector.py +345 -0
  7. app.json +25 -0
  8. app.py +787 -0
  9. identity_analyzer.py +216 -0
  10. optimax_reports.py +734 -0
  11. permission_analyzer.py +352 -0
  12. prompts.py +113 -0
  13. requirements.txt +50 -0
  14. runtime.txt +0 -0
  15. sharing_analyzer.py +129 -0
  16. vulenerabilities_db.py +272 -0
.gitattributes CHANGED
@@ -1,35 +1,35 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
Binary file (82 Bytes). View file
 
Procfile ADDED
Binary file (42 Bytes). View file
 
README.md CHANGED
@@ -1,12 +1,117 @@
1
- ---
2
- title: Agent Space
3
- emoji: 🌖
4
- colorFrom: purple
5
- colorTo: yellow
6
- sdk: gradio
7
- sdk_version: 6.5.1
8
- app_file: app.py
9
- pinned: false
10
- ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Optimax-agent
3
+ emoji: 🛡️
4
+ colorFrom: blue
5
+ colorTo: purple
6
+ sdk: gradio
7
+ sdk_version: 6.3.0
8
+ app_file: app.py
9
+ pinned: false
10
+ license: apache-2.0
11
+ ---
12
+
13
+ # 🛡️ Optimax Security Agent
14
+
15
+ AI-powered Salesforce security analysis API that receives metadata from Salesforce orgs and returns comprehensive security assessments.
16
+
17
+ ## Architecture
18
+
19
+ ```
20
+ Salesforce Org → Sends Metadata → Optimax Agent API → Returns Analysis
21
+ ```
22
+
23
+ **Key Feature:** This API processes **ONLY METADATA** - no credentials are ever received or stored.
24
+
25
+ ## Features
26
+
27
+ - 🤖 **AI-Powered Analysis** using Salesforce CodeGen 350M
28
+ - 🔍 **Permission Vulnerability Detection**
29
+ - 👤 **Identity & Access Management Analysis**
30
+ - 🔐 **Sharing Model Security Assessment**
31
+ - 📊 **Risk Scoring & Prioritization**
32
+ - 💡 **Actionable Recommendations**
33
+
34
+ ## API Endpoints
35
+
36
+ ### POST `/api/analyze`
37
+
38
+ Main analysis endpoint for Salesforce integration.
39
+
40
+ **Request:**
41
+ ```json
42
+ {
43
+ "org_id": "00Dxx0000001234",
44
+ "org_name": "Your Organization",
45
+ "users": [...],
46
+ "profiles": [...],
47
+ "permission_sets": [...],
48
+ "login_history": [...],
49
+ "sharing_settings": {...}
50
+ }
51
+ ```
52
+
53
+ **Response:**
54
+ ```json
55
+ {
56
+ "success": true,
57
+ "overall_risk_score": 45,
58
+ "risk_level": "MEDIUM",
59
+ "critical_findings": [...],
60
+ "high_findings": [...],
61
+ "ai_executive_summary": "...",
62
+ "ai_recommendations": [...]
63
+ }
64
+ ```
65
+
66
+ ## Usage
67
+
68
+ ### From Salesforce Apex:
69
+
70
+ ```apex
71
+ // 1. Collect metadata
72
+ Map<String, Object> metadata = MetadataExtractor.collectOrgMetadata();
73
+
74
+ // 2. Call Optimax Agent
75
+ HttpRequest req = new HttpRequest();
76
+ req.setEndpoint('callout:Optimax_Agent/api/analyze');
77
+ req.setMethod('POST');
78
+ req.setHeader('Content-Type', 'application/json');
79
+ req.setBody(JSON.serialize(metadata));
80
+
81
+ Http http = new Http();
82
+ HttpResponse res = http.send(req);
83
+
84
+ // 3. Parse response
85
+ Map<String, Object> analysis = (Map<String, Object>)JSON.deserializeUntyped(res.getBody());
86
+ ```
87
+
88
+ ### Using cURL:
89
+
90
+ ```bash
91
+ curl -X POST https://m8077anya-vishwakarma-optimax-agent.hf.space/api/analyze \
92
+ -H "Content-Type: application/json" \
93
+ -d @metadata.json
94
+ ```
95
+
96
+ ## Testing
97
+
98
+ Visit the app in your browser to use the interactive testing interface.
99
+
100
+ ## Security
101
+
102
+ - ✅ No credential processing
103
+ - ✅ Metadata-only analysis
104
+ - ✅ Stateless operation
105
+ - ✅ Private space (access controlled)
106
+
107
+ ## Technical Details
108
+
109
+ - **Framework:** Gradio + FastAPI
110
+ - **AI Model:** Salesforce CodeGen 350M (350 million parameters)
111
+ - **Analysis:** Hybrid (AI + rule-based)
112
+ - **Deployment:** Hugging Face Spaces
113
+
114
+ ---
115
+
116
+ **Version:** 2.0.0
117
+ **Last Updated:** January 2026
ai_models.py ADDED
@@ -0,0 +1,838 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ ai_models.py - Multi-Model AI Security Analyzer for Salesforce
3
+
4
+ Uses FOUR models for comprehensive analysis:
5
+ 1. CodeBERT (125M) - For Apex code analysis
6
+ 2. RoBERTa (125M) - For permission classification
7
+ 3. SecBERT (110M) - For security policy analysis
8
+ 4. Isolation Forest - For anomaly detection (NEW)
9
+
10
+ Usage:
11
+ from ai_models import MultiModelAnalyzer
12
+
13
+ analyzer = MultiModelAnalyzer()
14
+ results = analyzer.analyze_org(metadata)
15
+ """
16
+
17
+ import torch
18
+ import logging
19
+ from transformers import (
20
+ RobertaTokenizer,
21
+ RobertaModel,
22
+ AutoTokenizer,
23
+ AutoModel
24
+ )
25
+ from sklearn.metrics.pairwise import cosine_similarity
26
+ from sklearn.ensemble import IsolationForest # NEW: Anomaly detection
27
+ import numpy as np
28
+
29
+ logger = logging.getLogger(__name__)
30
+
31
+ # Import our anomaly detector
32
+ try:
33
+ from anomaly_detector import AnomalyDetector
34
+ ANOMALY_AVAILABLE = True
35
+ except ImportError:
36
+ logger.warning("⚠️ anomaly_detector.py not found - anomaly detection disabled")
37
+ ANOMALY_AVAILABLE = False
38
+
39
+ # ============================================================================
40
+ # MULTI-MODEL ANALYZER
41
+ # ============================================================================
42
+
43
+ class MultiModelAnalyzer:
44
+ """
45
+ Multi-model AI analyzer using CodeBERT, RoBERTa, SecBERT, and Isolation Forest
46
+ """
47
+
48
+ def __init__(self):
49
+ """Initialize all four models"""
50
+ self.available = False
51
+
52
+ try:
53
+ logger.info("🤖 Loading AI Models...")
54
+ logger.info(" 1/4 Loading CodeBERT for code analysis...")
55
+ self.codebert = CodeBERTAnalyzer()
56
+
57
+ logger.info(" 2/4 Loading RoBERTa for permission classification...")
58
+ self.roberta = RoBERTaAnalyzer()
59
+
60
+ logger.info(" 3/4 Loading SecBERT for security analysis...")
61
+ self.secbert = SecBERTAnalyzer()
62
+
63
+ logger.info(" 4/4 Loading Anomaly Detector...")
64
+ if ANOMALY_AVAILABLE:
65
+ self.anomaly = AnomalyDetector()
66
+ logger.info(" ✅ Anomaly detector loaded!")
67
+ else:
68
+ self.anomaly = None
69
+ logger.warning(" ⚠️ Anomaly detector not available")
70
+
71
+ self.available = True
72
+ logger.info("✅ All AI models loaded successfully!")
73
+
74
+ except Exception as e:
75
+ logger.error(f"❌ Failed to load AI models: {e}")
76
+ self.available = False
77
+
78
+ def analyze_org(self, metadata):
79
+ """
80
+ Analyze entire Salesforce org with all models
81
+
82
+ Args:
83
+ metadata: Dict containing org data (users, profiles, permission_sets, etc.)
84
+
85
+ Returns:
86
+ Dict with AI-powered insights from all models
87
+ """
88
+ if not self.available:
89
+ return {
90
+ 'available': False,
91
+ 'message': 'AI models not available'
92
+ }
93
+
94
+ results = {
95
+ 'available': True,
96
+ 'code_analysis': [],
97
+ 'permission_analysis': [],
98
+ 'security_analysis': [],
99
+ 'anomaly_analysis': [], # NEW
100
+ 'overall_risk_score': 0,
101
+ 'ai_recommendations': []
102
+ }
103
+
104
+ try:
105
+ # 1. Analyze Apex code (if available)
106
+ if 'apex_classes' in metadata and metadata['apex_classes']:
107
+ logger.info("🔍 Analyzing Apex code with CodeBERT...")
108
+ results['code_analysis'] = self.codebert.analyze_apex_code(
109
+ metadata['apex_classes']
110
+ )
111
+
112
+ # 2. Analyze permissions with RoBERTa
113
+ if 'permission_sets' in metadata or 'profiles' in metadata:
114
+ logger.info("🔍 Analyzing permissions with RoBERTa...")
115
+ results['permission_analysis'] = self.roberta.analyze_permissions(
116
+ permission_sets=metadata.get('permission_sets', []),
117
+ profiles=metadata.get('profiles', [])
118
+ )
119
+
120
+ # 3. Analyze security policies with SecBERT
121
+ logger.info("🔍 Analyzing security policies with SecBERT...")
122
+ results['security_analysis'] = self.secbert.analyze_security(
123
+ metadata=metadata
124
+ )
125
+
126
+ # 4. Detect anomalies with Isolation Forest (NEW)
127
+ if self.anomaly:
128
+ logger.info("🔍 Detecting behavioral anomalies...")
129
+
130
+ # Login anomalies
131
+ login_anomalies = self.anomaly.detect_login_anomalies(
132
+ metadata.get('login_history', [])
133
+ )
134
+
135
+ # Permission anomalies
136
+ perm_anomalies = self.anomaly.detect_permission_anomalies(
137
+ users=metadata.get('users', []),
138
+ permission_sets=metadata.get('permission_sets', [])
139
+ )
140
+
141
+ # Dormant accounts
142
+ dormant_anomalies = self.anomaly.detect_dormant_account_risks(
143
+ metadata.get('users', [])
144
+ )
145
+
146
+ results['anomaly_analysis'] = login_anomalies + perm_anomalies + dormant_anomalies
147
+ logger.info(f" Found {len(results['anomaly_analysis'])} anomalies")
148
+
149
+ # 5. Calculate overall AI risk score
150
+ results['overall_risk_score'] = self._calculate_ai_risk_score(results)
151
+
152
+ # 6. Generate AI recommendations
153
+ results['ai_recommendations'] = self._generate_recommendations(results)
154
+
155
+ logger.info(f"✅ AI Analysis complete - Risk Score: {results['overall_risk_score']}/100")
156
+
157
+ except Exception as e:
158
+ logger.error(f"❌ AI analysis error: {e}")
159
+ results['error'] = str(e)
160
+
161
+ return results
162
+
163
+ def _calculate_ai_risk_score(self, results):
164
+ """Calculate overall risk score from all model outputs"""
165
+ score = 0
166
+
167
+ # Code vulnerabilities
168
+ code_issues = len(results.get('code_analysis', []))
169
+ score += min(25, code_issues * 10)
170
+
171
+ # Permission risks
172
+ high_risk_perms = len([p for p in results.get('permission_analysis', [])
173
+ if p.get('risk_level') in ['CRITICAL', 'HIGH']])
174
+ score += min(30, high_risk_perms * 8)
175
+
176
+ # Security policy issues
177
+ security_issues = len(results.get('security_analysis', []))
178
+ score += min(25, security_issues * 5)
179
+
180
+ # Anomalies (NEW)
181
+ anomaly_issues = len(results.get('anomaly_analysis', []))
182
+ high_anomalies = len([a for a in results.get('anomaly_analysis', [])
183
+ if a.get('severity') == 'High'])
184
+ score += min(20, high_anomalies * 10 + (anomaly_issues - high_anomalies) * 3)
185
+
186
+ return min(100, score)
187
+
188
+ def _generate_recommendations(self, results):
189
+ """Generate AI-powered recommendations"""
190
+ recommendations = []
191
+
192
+ if results.get('code_analysis'):
193
+ recommendations.append(
194
+ "🔍 Code vulnerabilities detected - Review Apex classes for security issues"
195
+ )
196
+
197
+ if results.get('permission_analysis'):
198
+ high_risk = [p for p in results['permission_analysis']
199
+ if p.get('risk_level') == 'CRITICAL']
200
+ if high_risk:
201
+ recommendations.append(
202
+ f"⚠️ {len(high_risk)} critical permission risks - Implement least privilege"
203
+ )
204
+
205
+ if results.get('security_analysis'):
206
+ recommendations.append(
207
+ "🛡️ Security policy gaps identified - Strengthen access controls"
208
+ )
209
+
210
+ # NEW: Anomaly recommendations
211
+ if results.get('anomaly_analysis'):
212
+ high_anomalies = [a for a in results['anomaly_analysis']
213
+ if a.get('severity') == 'High']
214
+ if high_anomalies:
215
+ recommendations.append(
216
+ f"🚨 {len(high_anomalies)} high-risk behavioral anomalies - Investigate immediately"
217
+ )
218
+
219
+ login_anomalies = [a for a in results['anomaly_analysis']
220
+ if 'Login' in a.get('type', '')]
221
+ if login_anomalies:
222
+ recommendations.append(
223
+ f"🔐 {len(login_anomalies)} unusual login patterns - Enable MFA and IP restrictions"
224
+ )
225
+
226
+ if not recommendations:
227
+ recommendations.append("✅ No critical AI-detected issues found")
228
+
229
+ return recommendations
230
+
231
+
232
+ # ============================================================================
233
+ # CODEBERT ANALYZER (For Apex Code)
234
+ # ============================================================================
235
+
236
+ class CodeBERTAnalyzer:
237
+ """
238
+ CodeBERT model for analyzing Apex code
239
+ Zero-shot vulnerability detection using embeddings
240
+ """
241
+
242
+ def __init__(self):
243
+ self.model_name = "microsoft/codebert-base"
244
+ self.tokenizer = RobertaTokenizer.from_pretrained(self.model_name)
245
+ self.model = RobertaModel.from_pretrained(self.model_name)
246
+ self.model.eval()
247
+
248
+ # Vulnerability patterns (embeddings will be compared against these)
249
+ self.vulnerability_patterns = {
250
+ 'SOQL_INJECTION': [
251
+ 'dynamic SOQL query with string concatenation',
252
+ 'database query with user input without binding',
253
+ 'SQL injection vulnerability in Salesforce'
254
+ ],
255
+ 'MISSING_SHARING': [
256
+ 'class without sharing keywords',
257
+ 'missing with sharing declaration',
258
+ 'no sharing enforcement in Apex'
259
+ ],
260
+ 'HARDCODED_CREDENTIALS': [
261
+ 'hardcoded API key in code',
262
+ 'exposed credentials in source',
263
+ 'plaintext password in class'
264
+ ],
265
+ 'UNSAFE_DML': [
266
+ 'DML operation without try catch',
267
+ 'unhandled database operation',
268
+ 'unsafe insert update delete'
269
+ ]
270
+ }
271
+
272
+ # Pre-compute pattern embeddings
273
+ self.pattern_embeddings = self._compute_pattern_embeddings()
274
+
275
+ def _compute_pattern_embeddings(self):
276
+ """Pre-compute embeddings for vulnerability patterns"""
277
+ embeddings = {}
278
+
279
+ for vuln_type, patterns in self.vulnerability_patterns.items():
280
+ pattern_embs = []
281
+ for pattern in patterns:
282
+ emb = self._get_embedding(pattern)
283
+ pattern_embs.append(emb)
284
+ embeddings[vuln_type] = np.mean(pattern_embs, axis=0)
285
+
286
+ return embeddings
287
+
288
+ def _get_embedding(self, text):
289
+ """Get embedding for text using CodeBERT"""
290
+ inputs = self.tokenizer(
291
+ text,
292
+ return_tensors='pt',
293
+ max_length=512,
294
+ truncation=True,
295
+ padding=True
296
+ )
297
+
298
+ with torch.no_grad():
299
+ outputs = self.model(**inputs)
300
+ # Use [CLS] token embedding
301
+ embedding = outputs.last_hidden_state[:, 0, :].numpy()
302
+
303
+ return embedding[0]
304
+
305
+ def analyze_code_snippet(self, code):
306
+ """Analyze a single code snippet"""
307
+ # Get code embedding
308
+ code_embedding = self._get_embedding(code)
309
+
310
+ # Compare with vulnerability patterns
311
+ vulnerabilities = []
312
+
313
+ for vuln_type, pattern_emb in self.pattern_embeddings.items():
314
+ similarity = cosine_similarity(
315
+ code_embedding.reshape(1, -1),
316
+ pattern_emb.reshape(1, -1)
317
+ )[0][0]
318
+
319
+ # Threshold for detection (tune this based on testing)
320
+ if similarity > 0.7:
321
+ vulnerabilities.append({
322
+ 'type': vuln_type,
323
+ 'confidence': float(similarity),
324
+ 'severity': 'HIGH' if similarity > 0.85 else 'MEDIUM'
325
+ })
326
+
327
+ # Also use pattern matching as backup
328
+ pattern_matches = self._pattern_match(code)
329
+ vulnerabilities.extend(pattern_matches)
330
+
331
+ return vulnerabilities
332
+
333
+ def _pattern_match(self, code):
334
+ """Simple pattern matching for common issues"""
335
+ issues = []
336
+
337
+ if 'Database.query(' in code and '+' in code:
338
+ issues.append({
339
+ 'type': 'POTENTIAL_SOQL_INJECTION',
340
+ 'confidence': 0.9,
341
+ 'severity': 'CRITICAL',
342
+ 'description': 'Dynamic SOQL with string concatenation detected'
343
+ })
344
+
345
+ if ('public class' in code and
346
+ 'with sharing' not in code and
347
+ 'without sharing' not in code):
348
+ issues.append({
349
+ 'type': 'MISSING_SHARING',
350
+ 'confidence': 0.95,
351
+ 'severity': 'HIGH',
352
+ 'description': 'Class missing sharing declaration'
353
+ })
354
+
355
+ # Check for potential hardcoded credentials
356
+ credential_keywords = ['password', 'apikey', 'api_key', 'secret', 'token']
357
+ for keyword in credential_keywords:
358
+ if f'{keyword} =' in code.lower() or f'{keyword}=' in code.lower():
359
+ issues.append({
360
+ 'type': 'POTENTIAL_HARDCODED_CREDENTIALS',
361
+ 'confidence': 0.8,
362
+ 'severity': 'CRITICAL',
363
+ 'description': f'Possible hardcoded {keyword} found'
364
+ })
365
+ break
366
+
367
+ return issues
368
+
369
+ def analyze_apex_code(self, apex_classes):
370
+ """Analyze multiple Apex classes"""
371
+ results = []
372
+
373
+ for apex_class in apex_classes[:10]: # Limit to avoid timeout
374
+ class_name = apex_class.get('Name', 'Unknown')
375
+ code = apex_class.get('Body', '')
376
+
377
+ if not code:
378
+ continue
379
+
380
+ vulnerabilities = self.analyze_code_snippet(code)
381
+
382
+ if vulnerabilities:
383
+ results.append({
384
+ 'class_name': class_name,
385
+ 'vulnerabilities': vulnerabilities,
386
+ 'total_issues': len(vulnerabilities)
387
+ })
388
+
389
+ return results
390
+
391
+
392
+ # ============================================================================
393
+ # ROBERTA ANALYZER (For Permission Classification)
394
+ # ============================================================================
395
+
396
+ class RoBERTaAnalyzer:
397
+ """
398
+ RoBERTa model for permission risk classification
399
+ Zero-shot classification using semantic similarity
400
+ """
401
+
402
+ def __init__(self):
403
+ self.model_name = "roberta-base"
404
+ self.tokenizer = AutoTokenizer.from_pretrained(self.model_name)
405
+ self.model = AutoModel.from_pretrained(self.model_name)
406
+ self.model.eval()
407
+
408
+ # Risk level definitions
409
+ self.risk_definitions = {
410
+ 'CRITICAL': [
411
+ 'full system access with modify all data',
412
+ 'administrative privileges with user management',
413
+ 'unrestricted access to all records',
414
+ 'complete control over organization data'
415
+ ],
416
+ 'HIGH': [
417
+ 'elevated permissions with data modification',
418
+ 'access to sensitive user information',
419
+ 'ability to change security settings',
420
+ 'export capabilities for all data'
421
+ ],
422
+ 'MEDIUM': [
423
+ 'limited administrative functions',
424
+ 'read access to multiple objects',
425
+ 'standard user permissions with extras',
426
+ 'moderate data access rights'
427
+ ],
428
+ 'LOW': [
429
+ 'basic user permissions',
430
+ 'read-only access to owned records',
431
+ 'minimal system privileges',
432
+ 'restricted data access'
433
+ ]
434
+ }
435
+
436
+ # Pre-compute risk embeddings
437
+ self.risk_embeddings = self._compute_risk_embeddings()
438
+
439
+ def _compute_risk_embeddings(self):
440
+ """Pre-compute embeddings for risk levels"""
441
+ embeddings = {}
442
+
443
+ for risk_level, descriptions in self.risk_definitions.items():
444
+ risk_embs = []
445
+ for desc in descriptions:
446
+ emb = self._get_embedding(desc)
447
+ risk_embs.append(emb)
448
+ embeddings[risk_level] = np.mean(risk_embs, axis=0)
449
+
450
+ return embeddings
451
+
452
+ def _get_embedding(self, text):
453
+ """Get embedding using RoBERTa"""
454
+ inputs = self.tokenizer(
455
+ text,
456
+ return_tensors='pt',
457
+ max_length=512,
458
+ truncation=True,
459
+ padding=True
460
+ )
461
+
462
+ with torch.no_grad():
463
+ outputs = self.model(**inputs)
464
+ embedding = outputs.last_hidden_state[:, 0, :].numpy()
465
+
466
+ return embedding[0]
467
+
468
+ def classify_permission_set(self, permission_set):
469
+ """Classify a single permission set's risk level"""
470
+
471
+ # Build description from permissions
472
+ dangerous_perms = []
473
+
474
+ if permission_set.get('PermissionsModifyAllData'):
475
+ dangerous_perms.append('modify all data')
476
+ if permission_set.get('PermissionsViewAllData'):
477
+ dangerous_perms.append('view all data')
478
+ if permission_set.get('PermissionsManageUsers'):
479
+ dangerous_perms.append('manage users')
480
+ if permission_set.get('PermissionsAuthorApex'):
481
+ dangerous_perms.append('author apex code')
482
+
483
+ if not dangerous_perms:
484
+ return {
485
+ 'name': permission_set.get('Name', 'Unknown'),
486
+ 'risk_level': 'LOW',
487
+ 'confidence': 0.9,
488
+ 'dangerous_permissions': []
489
+ }
490
+
491
+ # Create description
492
+ description = f"permission set with {', '.join(dangerous_perms)}"
493
+
494
+ # Get embedding
495
+ perm_embedding = self._get_embedding(description)
496
+
497
+ # Compare with risk levels
498
+ similarities = {}
499
+ for risk_level, risk_emb in self.risk_embeddings.items():
500
+ similarity = cosine_similarity(
501
+ perm_embedding.reshape(1, -1),
502
+ risk_emb.reshape(1, -1)
503
+ )[0][0]
504
+ similarities[risk_level] = similarity
505
+
506
+ # Get highest similarity
507
+ predicted_risk = max(similarities, key=similarities.get)
508
+ confidence = similarities[predicted_risk]
509
+
510
+ return {
511
+ 'name': permission_set.get('Name', 'Unknown'),
512
+ 'risk_level': predicted_risk,
513
+ 'confidence': float(confidence),
514
+ 'dangerous_permissions': dangerous_perms
515
+ }
516
+
517
+ def analyze_permissions(self, permission_sets, profiles):
518
+ """Analyze all permission sets and profiles"""
519
+ results = []
520
+
521
+ # Analyze permission sets
522
+ for perm_set in permission_sets[:20]: # Limit for performance
523
+ result = self.classify_permission_set(perm_set)
524
+ result['type'] = 'Permission Set'
525
+ results.append(result)
526
+
527
+ # Analyze profiles
528
+ for profile in profiles[:10]:
529
+ result = self.classify_permission_set(profile)
530
+ result['type'] = 'Profile'
531
+ results.append(result)
532
+
533
+ return results
534
+
535
+
536
+ # ============================================================================
537
+ # SECBERT ANALYZER (For Security Policies)
538
+ # ============================================================================
539
+
540
+ class SecBERTAnalyzer:
541
+ """
542
+ SecBERT model for security policy analysis
543
+ Detects security misconfigurations and policy violations
544
+ """
545
+
546
+ def __init__(self):
547
+ # SecBERT might not be available, fallback to RoBERTa
548
+ try:
549
+ self.model_name = "jackaduma/SecBERT"
550
+ self.tokenizer = AutoTokenizer.from_pretrained(self.model_name)
551
+ self.model = AutoModel.from_pretrained(self.model_name)
552
+ except:
553
+ logger.warning("⚠️ SecBERT not available, using RoBERTa as fallback")
554
+ self.model_name = "roberta-base"
555
+ self.tokenizer = AutoTokenizer.from_pretrained(self.model_name)
556
+ self.model = AutoModel.from_pretrained(self.model_name)
557
+
558
+ self.model.eval()
559
+
560
+ # Security violation patterns
561
+ self.security_patterns = {
562
+ 'MFA_NOT_ENFORCED': [
563
+ 'multi-factor authentication not enabled',
564
+ 'no MFA requirement for users',
565
+ 'missing two-factor authentication'
566
+ ],
567
+ 'EXCESSIVE_ADMINS': [
568
+ 'too many administrative accounts',
569
+ 'excessive system administrator privileges',
570
+ 'multiple users with full access'
571
+ ],
572
+ 'DORMANT_ACCOUNTS': [
573
+ 'inactive user accounts with permissions',
574
+ 'dormant administrative accounts',
575
+ 'unused accounts with access rights'
576
+ ],
577
+ 'WEAK_PASSWORD_POLICY': [
578
+ 'insufficient password requirements',
579
+ 'weak password complexity rules',
580
+ 'no password expiration policy'
581
+ ]
582
+ }
583
+
584
+ self.pattern_embeddings = self._compute_pattern_embeddings()
585
+
586
+ def _compute_pattern_embeddings(self):
587
+ """Pre-compute embeddings for security patterns"""
588
+ embeddings = {}
589
+
590
+ for pattern_type, descriptions in self.security_patterns.items():
591
+ pattern_embs = []
592
+ for desc in descriptions:
593
+ emb = self._get_embedding(desc)
594
+ pattern_embs.append(emb)
595
+ embeddings[pattern_type] = np.mean(pattern_embs, axis=0)
596
+
597
+ return embeddings
598
+
599
+ def _get_embedding(self, text):
600
+ """Get embedding using SecBERT/RoBERTa"""
601
+ inputs = self.tokenizer(
602
+ text,
603
+ return_tensors='pt',
604
+ max_length=512,
605
+ truncation=True,
606
+ padding=True
607
+ )
608
+
609
+ with torch.no_grad():
610
+ outputs = self.model(**inputs)
611
+ embedding = outputs.last_hidden_state[:, 0, :].numpy()
612
+
613
+ return embedding[0]
614
+
615
+ def analyze_security(self, metadata):
616
+ """Analyze security policies and configurations"""
617
+ findings = []
618
+
619
+ users = metadata.get('users', [])
620
+ profiles = metadata.get('profiles', [])
621
+
622
+ # Check MFA compliance
623
+ mfa_result = self._check_mfa_compliance(users)
624
+ if mfa_result:
625
+ findings.append(mfa_result)
626
+
627
+ # Check admin count
628
+ admin_result = self._check_admin_count(users, profiles)
629
+ if admin_result:
630
+ findings.append(admin_result)
631
+
632
+ # Check dormant accounts
633
+ dormant_result = self._check_dormant_accounts(users)
634
+ if dormant_result:
635
+ findings.append(dormant_result)
636
+
637
+ return findings
638
+
639
+ def _check_mfa_compliance(self, users):
640
+ """Check MFA compliance using AI"""
641
+ # Simple heuristic for demo
642
+ total_users = len(users)
643
+ if total_users == 0:
644
+ return None
645
+
646
+ # In real scenario, check MfaEnabled field
647
+ # For demo, create finding
648
+ description = f"multi-factor authentication compliance check for {total_users} users"
649
+ emb = self._get_embedding(description)
650
+
651
+ mfa_pattern = self.pattern_embeddings['MFA_NOT_ENFORCED']
652
+ similarity = cosine_similarity(
653
+ emb.reshape(1, -1),
654
+ mfa_pattern.reshape(1, -1)
655
+ )[0][0]
656
+
657
+ if similarity > 0.6:
658
+ return {
659
+ 'type': 'MFA_NOT_ENFORCED',
660
+ 'severity': 'HIGH',
661
+ 'confidence': float(similarity),
662
+ 'description': 'MFA may not be enforced for all users',
663
+ 'recommendation': 'Enable MFA for all users, especially admins'
664
+ }
665
+
666
+ return None
667
+
668
+ def _check_admin_count(self, users, profiles):
669
+ """Check for excessive administrators"""
670
+ admin_count = len([u for u in users if 'Admin' in str(u.get('Profile', {}))])
671
+
672
+ if admin_count > 5:
673
+ description = f"{admin_count} administrative users with full system access"
674
+ emb = self._get_embedding(description)
675
+
676
+ pattern = self.pattern_embeddings['EXCESSIVE_ADMINS']
677
+ similarity = cosine_similarity(
678
+ emb.reshape(1, -1),
679
+ pattern.reshape(1, -1)
680
+ )[0][0]
681
+
682
+ return {
683
+ 'type': 'EXCESSIVE_ADMINS',
684
+ 'severity': 'MEDIUM',
685
+ 'confidence': float(similarity),
686
+ 'admin_count': admin_count,
687
+ 'description': f'{admin_count} users have System Administrator profile',
688
+ 'recommendation': 'Reduce admin count, use Permission Sets instead'
689
+ }
690
+
691
+ return None
692
+
693
+ def _check_dormant_accounts(self, users):
694
+ """Check for dormant user accounts"""
695
+ # Simple check - in production, check LastLoginDate
696
+ inactive_count = len([u for u in users if not u.get('IsActive', True)])
697
+
698
+ if inactive_count > 0:
699
+ description = f"{inactive_count} inactive user accounts still present"
700
+ emb = self._get_embedding(description)
701
+
702
+ pattern = self.pattern_embeddings['DORMANT_ACCOUNTS']
703
+ similarity = cosine_similarity(
704
+ emb.reshape(1, -1),
705
+ pattern.reshape(1, -1)
706
+ )[0][0]
707
+
708
+ return {
709
+ 'type': 'DORMANT_ACCOUNTS',
710
+ 'severity': 'LOW',
711
+ 'confidence': float(similarity),
712
+ 'inactive_count': inactive_count,
713
+ 'description': f'{inactive_count} inactive accounts found',
714
+ 'recommendation': 'Review and remove dormant accounts'
715
+ }
716
+
717
+ return None
718
+
719
+
720
+ # ============================================================================
721
+ # HELPER FUNCTIONS FOR INTEGRATION
722
+ # ============================================================================
723
+
724
+ def get_ai_summary(analysis_results):
725
+ """Generate human-readable summary from AI analysis"""
726
+
727
+ if not analysis_results.get('available'):
728
+ return "AI analysis not available"
729
+
730
+ summary_parts = []
731
+
732
+ # Code analysis summary
733
+ if analysis_results.get('code_analysis'):
734
+ code_issues = len(analysis_results['code_analysis'])
735
+ summary_parts.append(f"Detected {code_issues} code vulnerabilities")
736
+
737
+ # Permission analysis summary
738
+ if analysis_results.get('permission_analysis'):
739
+ critical = len([p for p in analysis_results['permission_analysis']
740
+ if p.get('risk_level') == 'CRITICAL'])
741
+ if critical > 0:
742
+ summary_parts.append(f"Found {critical} critical permission risks")
743
+
744
+ # Security analysis summary
745
+ if analysis_results.get('security_analysis'):
746
+ security_issues = len(analysis_results['security_analysis'])
747
+ summary_parts.append(f"Identified {security_issues} security policy gaps")
748
+
749
+ # NEW: Anomaly analysis summary
750
+ if analysis_results.get('anomaly_analysis'):
751
+ anomaly_count = len(analysis_results['anomaly_analysis'])
752
+ high_anomalies = len([a for a in analysis_results['anomaly_analysis']
753
+ if a.get('severity') == 'High'])
754
+ if high_anomalies > 0:
755
+ summary_parts.append(f"Detected {high_anomalies} high-risk behavioral anomalies")
756
+ elif anomaly_count > 0:
757
+ summary_parts.append(f"Detected {anomaly_count} behavioral anomalies")
758
+
759
+ if not summary_parts:
760
+ return "AI analysis completed - no critical issues detected"
761
+
762
+ return ". ".join(summary_parts) + "."
763
+
764
+
765
+ # ============================================================================
766
+ # EXAMPLE USAGE
767
+ # ============================================================================
768
+
769
+ if __name__ == "__main__":
770
+
771
+ print("=" * 80)
772
+ print("MULTI-MODEL AI ANALYZER - TEST")
773
+ print("=" * 80)
774
+
775
+ # Initialize analyzer
776
+ analyzer = MultiModelAnalyzer()
777
+
778
+ if not analyzer.available:
779
+ print("❌ AI models failed to load")
780
+ exit(1)
781
+
782
+ # Test data
783
+ test_metadata = {
784
+ 'apex_classes': [
785
+ {
786
+ 'Name': 'UnsafeController',
787
+ 'Body': '''
788
+ public class UnsafeController {
789
+ public void queryData(String input) {
790
+ String query = 'SELECT Id FROM User WHERE Name = \\'' + input + '\\'';
791
+ Database.query(query);
792
+ }
793
+ }
794
+ '''
795
+ }
796
+ ],
797
+ 'permission_sets': [
798
+ {
799
+ 'Name': 'AdminPermSet',
800
+ 'PermissionsModifyAllData': True,
801
+ 'PermissionsViewAllData': True,
802
+ 'PermissionsManageUsers': True
803
+ }
804
+ ],
805
+ 'profiles': [
806
+ {
807
+ 'Name': 'System Administrator',
808
+ 'PermissionsModifyAllData': True,
809
+ 'PermissionsViewAllData': True
810
+ }
811
+ ],
812
+ 'users': [
813
+ {'Id': '1', 'IsActive': True},
814
+ {'Id': '2', 'IsActive': True},
815
+ {'Id': '3', 'IsActive': False}
816
+ ],
817
+ 'login_history': []
818
+ }
819
+
820
+ # Run analysis
821
+ print("\n🔍 Running AI analysis...")
822
+ results = analyzer.analyze_org(test_metadata)
823
+
824
+ # Display results
825
+ print("\n📊 RESULTS:")
826
+ print(f" Overall AI Risk Score: {results['overall_risk_score']}/100")
827
+ print(f" Code Issues: {len(results.get('code_analysis', []))}")
828
+ print(f" Permission Risks: {len(results.get('permission_analysis', []))}")
829
+ print(f" Security Findings: {len(results.get('security_analysis', []))}")
830
+ print(f" Anomalies Detected: {len(results.get('anomaly_analysis', []))}")
831
+
832
+ print("\n💡 AI Recommendations:")
833
+ for rec in results.get('ai_recommendations', []):
834
+ print(f" • {rec}")
835
+
836
+ print("\n" + "=" * 80)
837
+ print("✅ Multi-Model AI Analysis Complete!")
838
+ print("=" * 80)
anomaly_detector.py ADDED
@@ -0,0 +1,345 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ anomaly_detector.py - Behavioral Anomaly Detection
3
+ Add this file to detect unusual login patterns and permission usage
4
+
5
+ Usage:
6
+ from anomaly_detector import AnomalyDetector
7
+
8
+ detector = AnomalyDetector()
9
+ anomalies = detector.detect_login_anomalies(login_history)
10
+ """
11
+
12
+ import numpy as np
13
+ from sklearn.ensemble import IsolationForest
14
+ from datetime import datetime, timedelta
15
+ import logging
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+ class AnomalyDetector:
20
+ """
21
+ Detects anomalous behavior in Salesforce org using Isolation Forest
22
+ """
23
+
24
+ def __init__(self):
25
+ """Initialize anomaly detector"""
26
+ self.login_detector = IsolationForest(
27
+ contamination=0.1, # Expect ~10% anomalies
28
+ random_state=42
29
+ )
30
+ self.available = True
31
+ logger.info("✅ Anomaly Detector initialized")
32
+
33
+ def detect_login_anomalies(self, login_history: list) -> list:
34
+ """
35
+ Detect anomalous login patterns
36
+
37
+ Args:
38
+ login_history: List of login records
39
+
40
+ Returns:
41
+ List of anomaly findings
42
+ """
43
+ if not login_history or len(login_history) < 10:
44
+ return []
45
+
46
+ findings = []
47
+
48
+ try:
49
+ # Extract features from login history
50
+ features = self._extract_login_features(login_history)
51
+
52
+ if len(features) < 10:
53
+ return []
54
+
55
+ # Fit and predict anomalies
56
+ predictions = self.login_detector.fit_predict(features)
57
+ scores = self.login_detector.score_samples(features)
58
+
59
+ # Find anomalies (prediction = -1)
60
+ for idx, (pred, score) in enumerate(zip(predictions, scores)):
61
+ if pred == -1: # Anomaly detected
62
+ login = login_history[idx]
63
+
64
+ # Determine anomaly type
65
+ anomaly_type = self._classify_login_anomaly(login, login_history)
66
+
67
+ findings.append({
68
+ 'type': f'Anomalous Login - {anomaly_type}',
69
+ 'severity': 'High' if score < -0.5 else 'Medium',
70
+ 'user_id': login.get('UserId', 'Unknown'),
71
+ 'login_time': login.get('LoginTime', 'Unknown'),
72
+ 'source_ip': login.get('SourceIp', 'Unknown'),
73
+ 'anomaly_score': float(score),
74
+ 'description': f'Unusual {anomaly_type.lower()} detected',
75
+ 'impact': 'Potential account compromise or unauthorized access',
76
+ 'recommendation': 'Investigate login context, verify with user, consider MFA'
77
+ })
78
+
79
+ logger.info(f"🔍 Detected {len(findings)} login anomalies")
80
+
81
+ except Exception as e:
82
+ logger.error(f"❌ Anomaly detection error: {e}")
83
+
84
+ return findings
85
+
86
+ def _extract_login_features(self, login_history: list) -> np.ndarray:
87
+ """
88
+ Extract numerical features from login history
89
+
90
+ Features:
91
+ - Hour of day (0-23)
92
+ - Day of week (0-6)
93
+ - Login success (0/1)
94
+ - IP address hash (numeric representation)
95
+ """
96
+ features = []
97
+
98
+ for login in login_history:
99
+ try:
100
+ login_time = login.get('LoginTime', '')
101
+ if not login_time:
102
+ continue
103
+
104
+ # Parse timestamp
105
+ dt = datetime.fromisoformat(login_time.replace('Z', '+00:00'))
106
+
107
+ # Extract features
108
+ hour = dt.hour
109
+ day_of_week = dt.weekday()
110
+ is_success = 1 if login.get('Status') == 'Success' else 0
111
+
112
+ # Simple IP hash (just use last octet for demo)
113
+ ip = login.get('SourceIp', '0.0.0.0')
114
+ ip_hash = hash(ip) % 1000
115
+
116
+ features.append([hour, day_of_week, is_success, ip_hash])
117
+
118
+ except Exception as e:
119
+ continue
120
+
121
+ return np.array(features) if features else np.array([])
122
+
123
+ def _classify_login_anomaly(self, login: dict, all_logins: list) -> str:
124
+ """
125
+ Classify the type of anomaly detected
126
+ """
127
+ login_time = login.get('LoginTime', '')
128
+ source_ip = login.get('SourceIp', '')
129
+ status = login.get('Status', '')
130
+
131
+ if not login_time:
132
+ return 'Unknown'
133
+
134
+ try:
135
+ dt = datetime.fromisoformat(login_time.replace('Z', '+00:00'))
136
+ hour = dt.hour
137
+
138
+ # Check time-based anomalies
139
+ if hour < 6 or hour > 22:
140
+ return 'Off-Hours Access'
141
+
142
+ # Check IP-based anomalies
143
+ common_ips = [l.get('SourceIp') for l in all_logins]
144
+ if source_ip not in common_ips[:10]: # Not in top 10 IPs
145
+ return 'Unusual Location'
146
+
147
+ # Check status-based anomalies
148
+ if status == 'Failed':
149
+ return 'Failed Login Attempt'
150
+
151
+ return 'Unusual Pattern'
152
+
153
+ except Exception:
154
+ return 'Unknown'
155
+
156
+ def detect_permission_anomalies(self, users: list, permission_sets: list) -> list:
157
+ """
158
+ Detect users with anomalous permission combinations
159
+
160
+ Args:
161
+ users: List of user records
162
+ permission_sets: List of permission set assignments
163
+
164
+ Returns:
165
+ List of permission anomaly findings
166
+ """
167
+ findings = []
168
+
169
+ try:
170
+ # Count permissions per user
171
+ user_perm_counts = {}
172
+
173
+ for ps in permission_sets:
174
+ user_id = ps.get('AssigneeId')
175
+ if user_id:
176
+ user_perm_counts[user_id] = user_perm_counts.get(user_id, 0) + 1
177
+
178
+ if not user_perm_counts:
179
+ return []
180
+
181
+ # Convert to array for anomaly detection
182
+ perm_counts = np.array(list(user_perm_counts.values())).reshape(-1, 1)
183
+
184
+ if len(perm_counts) < 5:
185
+ return []
186
+
187
+ # Detect anomalies
188
+ detector = IsolationForest(contamination=0.1, random_state=42)
189
+ predictions = detector.fit_predict(perm_counts)
190
+
191
+ # Find users with anomalous permission counts
192
+ for user_id, perm_count in user_perm_counts.items():
193
+ idx = list(user_perm_counts.keys()).index(user_id)
194
+
195
+ if predictions[idx] == -1: # Anomaly
196
+ # Find user details
197
+ user = next((u for u in users if u.get('Id') == user_id), {})
198
+
199
+ findings.append({
200
+ 'type': 'Anomalous Permission Assignment',
201
+ 'severity': 'Medium',
202
+ 'user_id': user_id,
203
+ 'username': user.get('Username', 'Unknown'),
204
+ 'permission_count': perm_count,
205
+ 'description': f'User has {perm_count} permission sets (unusual)',
206
+ 'impact': 'Potential privilege escalation or excessive access',
207
+ 'recommendation': 'Review permission assignments, ensure least privilege'
208
+ })
209
+
210
+ logger.info(f"🔍 Detected {len(findings)} permission anomalies")
211
+
212
+ except Exception as e:
213
+ logger.error(f"❌ Permission anomaly detection error: {e}")
214
+
215
+ return findings
216
+
217
+ def detect_dormant_account_risks(self, users: list) -> list:
218
+ """
219
+ Detect dormant accounts with elevated privileges
220
+
221
+ Args:
222
+ users: List of user records
223
+
224
+ Returns:
225
+ List of dormant account findings
226
+ """
227
+ findings = []
228
+ threshold_days = 90
229
+ threshold_date = datetime.now() - timedelta(days=threshold_days)
230
+
231
+ for user in users:
232
+ last_login = user.get('LastLoginDate')
233
+ profile = user.get('Profile', {}).get('Name', '')
234
+
235
+ # Check for dormant admin accounts
236
+ if 'Admin' in profile or 'System Administrator' in profile:
237
+
238
+ if not last_login:
239
+ findings.append({
240
+ 'type': 'Dormant Admin Account - Never Used',
241
+ 'severity': 'High',
242
+ 'user_id': user.get('Id'),
243
+ 'username': user.get('Username'),
244
+ 'profile': profile,
245
+ 'description': 'Admin account never logged in',
246
+ 'impact': 'Orphaned privileged account - prime target for attackers',
247
+ 'recommendation': 'Deactivate immediately or remove admin privileges'
248
+ })
249
+
250
+ elif isinstance(last_login, str):
251
+ try:
252
+ last_login_date = datetime.fromisoformat(last_login.replace('Z', '+00:00'))
253
+
254
+ if last_login_date < threshold_date:
255
+ days_inactive = (datetime.now() - last_login_date).days
256
+
257
+ findings.append({
258
+ 'type': 'Dormant Admin Account',
259
+ 'severity': 'High',
260
+ 'user_id': user.get('Id'),
261
+ 'username': user.get('Username'),
262
+ 'profile': profile,
263
+ 'days_inactive': days_inactive,
264
+ 'last_login': last_login,
265
+ 'description': f'Admin account inactive for {days_inactive} days',
266
+ 'impact': 'Orphaned privileged account - security risk',
267
+ 'recommendation': 'Deactivate or remove admin privileges'
268
+ })
269
+ except Exception:
270
+ pass
271
+
272
+ logger.info(f"🔍 Detected {len(findings)} dormant account risks")
273
+ return findings
274
+
275
+
276
+ # ============================================================================
277
+ # INTEGRATION HELPER
278
+ # ============================================================================
279
+
280
+ def analyze_with_anomaly_detection(metadata: dict) -> dict:
281
+ """
282
+ Run anomaly detection on org metadata
283
+
284
+ Args:
285
+ metadata: Org metadata from Salesforce
286
+
287
+ Returns:
288
+ Anomaly analysis results
289
+ """
290
+ detector = AnomalyDetector()
291
+
292
+ findings = []
293
+
294
+ # Detect login anomalies
295
+ login_findings = detector.detect_login_anomalies(
296
+ metadata.get('login_history', [])
297
+ )
298
+ findings.extend(login_findings)
299
+
300
+ # Detect permission anomalies
301
+ perm_findings = detector.detect_permission_anomalies(
302
+ users=metadata.get('users', []),
303
+ permission_sets=metadata.get('permission_sets', [])
304
+ )
305
+ findings.extend(perm_findings)
306
+
307
+ # Detect dormant account risks
308
+ dormant_findings = detector.detect_dormant_account_risks(
309
+ metadata.get('users', [])
310
+ )
311
+ findings.extend(dormant_findings)
312
+
313
+ return {
314
+ 'findings': findings,
315
+ 'total_anomalies': len(findings),
316
+ 'login_anomalies': len(login_findings),
317
+ 'permission_anomalies': len(perm_findings),
318
+ 'dormant_accounts': len(dormant_findings)
319
+ }
320
+
321
+
322
+ if __name__ == '__main__':
323
+ # Test
324
+ print("=" * 80)
325
+ print("ANOMALY DETECTOR - TEST")
326
+ print("=" * 80)
327
+
328
+ detector = AnomalyDetector()
329
+
330
+ # Test login data
331
+ test_logins = [
332
+ {'UserId': '1', 'LoginTime': '2026-01-19T09:00:00Z', 'SourceIp': '192.168.1.1', 'Status': 'Success'},
333
+ {'UserId': '2', 'LoginTime': '2026-01-19T03:00:00Z', 'SourceIp': '10.0.0.1', 'Status': 'Success'}, # Anomaly: 3 AM
334
+ {'UserId': '3', 'LoginTime': '2026-01-19T10:00:00Z', 'SourceIp': '192.168.1.1', 'Status': 'Success'},
335
+ ] * 5 # Repeat to have enough data
336
+
337
+ anomalies = detector.detect_login_anomalies(test_logins)
338
+
339
+ print(f"\n✅ Detected {len(anomalies)} anomalies")
340
+ for anomaly in anomalies:
341
+ print(f" • {anomaly['type']} - {anomaly['description']}")
342
+
343
+ print("\n" + "=" * 80)
344
+ print("✅ Anomaly Detection Test Complete!")
345
+ print("=" * 80)
app.json ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "Optimax Security Agent",
3
+ "description": "AI-powered Salesforce security analysis",
4
+ "keywords": ["salesforce", "security", "ai"],
5
+ "formation": {
6
+ "web": {
7
+ "quantity": 1,
8
+ "size": "standard-2x"
9
+ }
10
+ },
11
+ "env": {
12
+ "SF_CLIENT_ID": {
13
+ "description": "Your Salesforce Client ID",
14
+ "required": true
15
+ },
16
+ "SF_CLIENT_SECRET": {
17
+ "description": "Your Salesforce Client Secret",
18
+ "required": true
19
+ },
20
+ "SF_REDIRECT_URI": {
21
+ "description": "OAuth Redirect URI",
22
+ "value": "https://login.salesforce.com/services/oauth2/success"
23
+ }
24
+ }
25
+ }
app.py ADDED
@@ -0,0 +1,787 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import json
3
+ import logging
4
+ from datetime import datetime, timezone
5
+ from simple_salesforce import Salesforce
6
+ import requests
7
+ from urllib.parse import urlencode, urlparse, parse_qs, unquote
8
+ import secrets
9
+ import time
10
+
11
+ # Import AI models
12
+ from ai_models import MultiModelAnalyzer, get_ai_summary
13
+
14
+ # Import analyzers
15
+ from permission_analyzer import PermissionAnalyzer
16
+ from identity_analyzer import IdentityAnalyzer
17
+ from sharing_analyzer import SharingAnalyzer
18
+
19
+ # Import report generator
20
+ from optimax_reports import generate_report
21
+
22
+ # Setup logging
23
+ logging.basicConfig(level=logging.INFO)
24
+ logger = logging.getLogger(__name__)
25
+
26
+ # ============================================================================
27
+ # OAUTH CONFIGURATION
28
+ # ============================================================================
29
+
30
+ SF_CLIENT_ID = "3MVG9VMBZCsTL9hmy3bhf4_UX7eXivaplob0liijsZucnNjPHD1yTL4J6mxXSN42BHh9wwOhQLbrA_vsl.oqc"
31
+ SF_CLIENT_SECRET = "4AC7C18215B1312D5148278BDC01D327DE8BB282DF9C5B697733DAF0E47A736E"
32
+ SF_REDIRECT_URI = "https://login.salesforce.com/services/oauth2/success"
33
+
34
+ # Session storage
35
+ OAUTH_SESSIONS = {}
36
+
37
+ # ============================================================================
38
+ # INITIALIZE COMPONENTS
39
+ # ============================================================================
40
+
41
+ logger.info("=" * 80)
42
+ logger.info("🚀 INITIALIZING OPTIMAX SECURITY AGENT")
43
+ logger.info("=" * 80)
44
+
45
+ # Initialize rule-based analyzers
46
+ logger.info("📋 Loading rule-based analyzers...")
47
+ perm_analyzer = PermissionAnalyzer()
48
+ identity_analyzer = IdentityAnalyzer()
49
+ sharing_analyzer = SharingAnalyzer()
50
+ logger.info("✅ Rule-based analyzers loaded")
51
+
52
+ # Initialize AI models
53
+ logger.info("")
54
+ logger.info("🤖 Loading Multi-Model AI Analyzer...")
55
+ logger.info(" This may take 30-60 seconds on first run...")
56
+ try:
57
+ ai_analyzer = MultiModelAnalyzer()
58
+ if ai_analyzer.available:
59
+ logger.info("✅ AI Models loaded successfully!")
60
+ logger.info(" • CodeBERT (125M) - Code vulnerability detection")
61
+ logger.info(" • RoBERTa (125M) - Permission risk classification")
62
+ logger.info(" • SecBERT (110M) - Security policy analysis")
63
+ logger.info(" • Isolation Forest - Behavioral anomaly detection")
64
+ else:
65
+ logger.warning("⚠️ AI models failed to load - using rule-based analysis only")
66
+ ai_analyzer = None
67
+ except Exception as e:
68
+ logger.error(f"⚠️ AI models not available: {e}")
69
+ logger.warning(" Continuing with rule-based analysis only")
70
+ ai_analyzer = None
71
+
72
+ logger.info("")
73
+ logger.info("=" * 80)
74
+ logger.info("✅ ALL COMPONENTS INITIALIZED")
75
+ logger.info("=" * 80)
76
+
77
+ # ============================================================================
78
+ # OAUTH FUNCTIONS WITH AUTO CODE EXTRACTION
79
+ # ============================================================================
80
+
81
+ def check_credentials_configured():
82
+ """Check if OAuth credentials are properly configured"""
83
+ return "YOUR_" not in SF_CLIENT_ID and "YOUR_" not in SF_CLIENT_SECRET
84
+
85
+ def generate_oauth_url(org_type: str) -> tuple:
86
+ """Generate OAuth URL for user authorization with JavaScript auto-extraction"""
87
+
88
+ if not check_credentials_configured():
89
+ return "", "", "⚠️ OAuth credentials not configured"
90
+
91
+ # Generate unique session ID
92
+ session_id = secrets.token_urlsafe(32)
93
+ OAUTH_SESSIONS[session_id] = {
94
+ "status": "pending",
95
+ "org_type": org_type,
96
+ "created_at": datetime.now(timezone.utc),
97
+ "code": None
98
+ }
99
+
100
+ # Determine authorization endpoint
101
+ if org_type == "sandbox":
102
+ auth_endpoint = "https://test.salesforce.com/services/oauth2/authorize"
103
+ redirect_uri = "https://test.salesforce.com/services/oauth2/success"
104
+ else:
105
+ auth_endpoint = "https://login.salesforce.com/services/oauth2/authorize"
106
+ redirect_uri = SF_REDIRECT_URI
107
+
108
+ # Build OAuth parameters
109
+ params = {
110
+ "response_type": "code",
111
+ "client_id": SF_CLIENT_ID,
112
+ "redirect_uri": redirect_uri,
113
+ "scope": "full refresh_token",
114
+ "state": session_id, # Pass session ID for tracking
115
+ "prompt": "login" # Force fresh login every time
116
+ }
117
+
118
+ oauth_url = f"{auth_endpoint}?{urlencode(params)}"
119
+
120
+ logger.info("=" * 80)
121
+ logger.info(f"🔗 Generated OAuth URL for {org_type.upper()}")
122
+ logger.info(f" Session ID: {session_id}")
123
+ logger.info(f" Redirect URI: {redirect_uri}")
124
+ logger.info("=" * 80)
125
+
126
+ # Create instructions with JavaScript auto-extraction
127
+ instructions = f"""
128
+ ## ✅ Step 1: Click Authorization Link Below
129
+
130
+ Click the **blue button** to open Salesforce authorization.
131
+
132
+ ---
133
+
134
+ ## 🔐 Step 2: Log In & Allow Access
135
+
136
+ 1. **Log into your {org_type.title()} org**
137
+ 2. Click **"Allow"** to grant permissions
138
+ 3. The authorization code will be **automatically detected!** ✨
139
+
140
+ ---
141
+
142
+ ## ⏳ Step 3: Wait for Automatic Processing
143
+
144
+ Once you authorize:
145
+ - 🔍 The code will be **automatically extracted** from the URL
146
+ - 🚀 The security scan will **start automatically**
147
+ - 📊 Results will appear below
148
+
149
+ **No manual copying needed!** 🎉
150
+
151
+ ---
152
+
153
+ **Session ID:** `{session_id}`
154
+ """
155
+
156
+ return session_id, oauth_url, instructions
157
+
158
+ def connect_production_org():
159
+ """Connect to Production/Developer Org"""
160
+ return generate_oauth_url("production")
161
+
162
+ def connect_sandbox_org():
163
+ """Connect to Sandbox Org"""
164
+ return generate_oauth_url("sandbox")
165
+
166
+ def exchange_code_for_token(code: str, org_type: str) -> dict:
167
+ """Exchange authorization code for access token"""
168
+
169
+ # Determine token endpoint
170
+ if org_type == "sandbox":
171
+ token_endpoint = "https://test.salesforce.com/services/oauth2/token"
172
+ redirect_uri = "https://test.salesforce.com/services/oauth2/success"
173
+ else:
174
+ token_endpoint = "https://login.salesforce.com/services/oauth2/token"
175
+ redirect_uri = SF_REDIRECT_URI
176
+
177
+ # Clean and decode the authorization code
178
+ clean_code = unquote(code.strip())
179
+
180
+ logger.info(f"🔐 Exchanging code for {org_type} token...")
181
+ logger.info(f" Code length: {len(clean_code)} chars")
182
+
183
+ # Exchange code for token
184
+ response = requests.post(
185
+ token_endpoint,
186
+ data={
187
+ "grant_type": "authorization_code",
188
+ "client_id": SF_CLIENT_ID,
189
+ "client_secret": SF_CLIENT_SECRET,
190
+ "redirect_uri": redirect_uri,
191
+ "code": clean_code
192
+ },
193
+ headers={"Content-Type": "application/x-www-form-urlencoded"}
194
+ )
195
+
196
+ if response.status_code != 200:
197
+ try:
198
+ error_data = response.json()
199
+ logger.error(f"❌ Token exchange failed: {error_data}")
200
+ return {
201
+ "error": error_data.get("error", "unknown_error"),
202
+ "error_description": error_data.get("error_description", "Token exchange failed")
203
+ }
204
+ except:
205
+ logger.error(f"❌ Token exchange failed with status {response.status_code}")
206
+ return {
207
+ "error": "token_exchange_failed",
208
+ "error_description": f"HTTP {response.status_code}"
209
+ }
210
+
211
+ token_data = response.json()
212
+ logger.info("✅ Token exchange successful!")
213
+
214
+ return token_data
215
+
216
+ # ============================================================================
217
+ # CORE ANALYSIS FUNCTIONS (same as original)
218
+ # ============================================================================
219
+
220
+ def extract_metadata(sf: Salesforce) -> dict:
221
+ """Extract metadata from Salesforce org"""
222
+
223
+ logger.info("📡 Extracting org metadata...")
224
+ metadata = {}
225
+
226
+ try:
227
+ # Get users
228
+ logger.info(" • Fetching users...")
229
+ users_query = """
230
+ SELECT Id, Username, Name, Email, IsActive, Profile.Name, LastLoginDate,
231
+ CreatedDate, UserRole.Name
232
+ FROM User
233
+ WHERE IsActive = true
234
+ LIMIT 1000
235
+ """
236
+ users_result = sf.query(users_query)
237
+ metadata['users'] = users_result['records']
238
+ logger.info(f" ✅ Retrieved {len(metadata['users'])} users")
239
+
240
+ # Get permission sets
241
+ logger.info(" • Fetching permission sets...")
242
+ ps_query = """
243
+ SELECT Id, Name, Label, Description, IsOwnedByProfile,
244
+ PermissionsModifyAllData, PermissionsViewAllData, PermissionsManageUsers,
245
+ PermissionsCustomizeApplication, PermissionsAuthorApex,
246
+ (SELECT Id, AssigneeId, Assignee.Username, Assignee.Name, Assignee.Email
247
+ FROM Assignments)
248
+ FROM PermissionSet
249
+ WHERE IsOwnedByProfile = false
250
+ LIMIT 500
251
+ """
252
+ ps_result = sf.query(ps_query)
253
+ metadata['permission_sets'] = ps_result['records']
254
+ logger.info(f" ✅ Retrieved {len(metadata['permission_sets'])} permission sets")
255
+
256
+ # Get profiles
257
+ logger.info(" • Fetching profiles...")
258
+ profiles_query = """
259
+ SELECT Id, Name, UserLicense.Name, IsCustom,
260
+ PermissionsModifyAllData, PermissionsViewAllData, PermissionsManageUsers,
261
+ PermissionsCustomizeApplication, PermissionsAuthorApex
262
+ FROM Profile
263
+ LIMIT 100
264
+ """
265
+ profiles_result = sf.query(profiles_query)
266
+ metadata['profiles'] = profiles_result['records']
267
+ logger.info(f" ✅ Retrieved {len(metadata['profiles'])} profiles")
268
+
269
+ # Get org info
270
+ logger.info(" • Fetching org info...")
271
+ org_query = "SELECT Id, Name, OrganizationType, InstanceName FROM Organization LIMIT 1"
272
+ org_result = sf.query(org_query)
273
+ if org_result['records']:
274
+ metadata['organization'] = org_result['records'][0]
275
+ logger.info(f" ✅ Retrieved org info: {metadata['organization'].get('Name')}")
276
+
277
+ # Try to get login history
278
+ try:
279
+ logger.info(" • Fetching login history...")
280
+ login_query = """
281
+ SELECT Id, UserId, LoginTime, SourceIp, Status, LoginType
282
+ FROM LoginHistory
283
+ WHERE LoginTime = LAST_N_DAYS:30
284
+ LIMIT 1000
285
+ """
286
+ login_result = sf.query(login_query)
287
+ metadata['login_history'] = login_result['records']
288
+ logger.info(f" ✅ Retrieved {len(metadata['login_history'])} login records")
289
+ except Exception as e:
290
+ logger.warning(f" ⚠️ Could not fetch login history: {e}")
291
+ metadata['login_history'] = []
292
+
293
+ logger.info("✅ Metadata extraction complete!")
294
+ return metadata
295
+
296
+ except Exception as e:
297
+ logger.error(f"❌ Error extracting metadata: {e}")
298
+ raise
299
+
300
+ def calculate_risk_score(findings: list) -> tuple:
301
+ """Calculate overall risk score based on findings"""
302
+
303
+ score = 0
304
+ severity_weights = {
305
+ 'Critical': 30,
306
+ 'High': 15,
307
+ 'Medium': 5,
308
+ 'Low': 1
309
+ }
310
+
311
+ for finding in findings:
312
+ severity = finding.get('severity', 'Low')
313
+ score += severity_weights.get(severity, 0)
314
+
315
+ # Normalize to 0-100
316
+ score = min(100, score)
317
+
318
+ # Determine risk level
319
+ if score >= 70:
320
+ risk_level = "CRITICAL"
321
+ elif score >= 50:
322
+ risk_level = "HIGH"
323
+ elif score >= 30:
324
+ risk_level = "MEDIUM"
325
+ else:
326
+ risk_level = "LOW"
327
+
328
+ return score, risk_level
329
+
330
+ def analyze_org(metadata: dict) -> dict:
331
+ """Perform comprehensive security analysis"""
332
+
333
+ logger.info("")
334
+ logger.info("=" * 80)
335
+ logger.info("🔍 STARTING SECURITY ANALYSIS")
336
+ logger.info("=" * 80)
337
+
338
+ all_findings = []
339
+
340
+ # Create user lookup for analyzers
341
+ user_lookup = {u.get('Id'): u for u in metadata.get('users', [])}
342
+
343
+ # 1. Permission Analysis
344
+ logger.info("1️⃣ Analyzing permissions...")
345
+ perm_results = perm_analyzer.analyze_all_permissions(
346
+ metadata.get('permission_sets', []),
347
+ metadata.get('profiles', []),
348
+ metadata.get('users', [])
349
+ )
350
+ all_findings.extend(perm_results['findings'])
351
+ logger.info(f" ✅ Found {len(perm_results['findings'])} permission issues")
352
+
353
+ # 2. Identity Analysis
354
+ logger.info("2️⃣ Analyzing identity & access...")
355
+ identity_results = identity_analyzer.analyze_users(
356
+ metadata.get('users', []),
357
+ metadata.get('login_history', [])
358
+ )
359
+ all_findings.extend(identity_results['findings'])
360
+ logger.info(f" ✅ Found {len(identity_results['findings'])} identity issues")
361
+
362
+ # 3. Sharing Analysis
363
+ logger.info("3️⃣ Analyzing sharing model...")
364
+ sharing_results = sharing_analyzer.analyze_sharing_settings({
365
+ 'organization_wide_defaults': [],
366
+ 'role_hierarchy': [],
367
+ 'sharing_rules': []
368
+ })
369
+ all_findings.extend(sharing_results['findings'])
370
+ logger.info(f" ✅ Found {len(sharing_results['findings'])} sharing issues")
371
+
372
+ # 4. AI Analysis
373
+ ai_summary = ""
374
+ ai_recommendations = []
375
+
376
+ if ai_analyzer and ai_analyzer.available:
377
+ logger.info("4️⃣ Running AI analysis...")
378
+ try:
379
+ ai_analysis = ai_analyzer.analyze_security(all_findings, metadata)
380
+ ai_summary = ai_analysis.get('executive_summary', '')
381
+ ai_recommendations = ai_analysis.get('recommendations', [])
382
+
383
+ for finding in all_findings:
384
+ if finding.get('type') in ai_analysis.get('ai_insights', {}):
385
+ finding['ai_insight'] = ai_analysis['ai_insights'][finding['type']]
386
+
387
+ logger.info(f" ✅ AI analysis complete")
388
+ except Exception as e:
389
+ logger.warning(f" ⚠️ AI analysis failed: {e}")
390
+ else:
391
+ logger.info("4️⃣ Skipping AI analysis (not available)")
392
+
393
+ # Calculate risk score
394
+ risk_score, risk_level = calculate_risk_score(all_findings)
395
+
396
+ # Organize findings by severity
397
+ findings_by_severity = {
398
+ 'Critical': [f for f in all_findings if f.get('severity') == 'Critical'],
399
+ 'High': [f for f in all_findings if f.get('severity') == 'High'],
400
+ 'Medium': [f for f in all_findings if f.get('severity') == 'Medium'],
401
+ 'Low': [f for f in all_findings if f.get('severity') == 'Low']
402
+ }
403
+
404
+ logger.info("")
405
+ logger.info("=" * 80)
406
+ logger.info("✅ ANALYSIS COMPLETE")
407
+ logger.info(f" Risk Score: {risk_score}/100 ({risk_level})")
408
+ logger.info(f" Total Findings: {len(all_findings)}")
409
+ logger.info("=" * 80)
410
+
411
+ return {
412
+ 'success': True,
413
+ 'overall_risk_score': risk_score,
414
+ 'risk_level': risk_level,
415
+ 'total_findings': len(all_findings),
416
+ 'findings_by_severity': findings_by_severity,
417
+ 'all_findings': all_findings,
418
+ 'ai_executive_summary': ai_summary,
419
+ 'ai_recommendations': ai_recommendations,
420
+ 'metadata_summary': {
421
+ 'total_users': len(metadata.get('users', [])),
422
+ 'active_users': len([u for u in metadata.get('users', []) if u.get('IsActive')]),
423
+ 'permission_sets': len(metadata.get('permission_sets', [])),
424
+ 'profiles': len(metadata.get('profiles', [])),
425
+ 'org_name': metadata.get('organization', {}).get('Name', 'Unknown')
426
+ }
427
+ }
428
+
429
+ def scan_salesforce_org(session_id: str, auth_code_or_url: str):
430
+ """
431
+ Scan Salesforce org using authorization code
432
+ Accepts either the full URL or just the code
433
+ """
434
+
435
+ logger.info("=" * 80)
436
+ logger.info(f"🚀 SCAN STARTED - Session: {session_id}")
437
+ logger.info("=" * 80)
438
+
439
+ # Validate session
440
+ if session_id not in OAUTH_SESSIONS:
441
+ error_msg = "❌ Invalid session ID. Please click 'Connect' again."
442
+ logger.error(error_msg)
443
+ return f"<div style='color: red; padding: 20px;'>{error_msg}</div>", json.dumps({"error": error_msg}, indent=2), None
444
+
445
+ session = OAUTH_SESSIONS[session_id]
446
+ org_type = session.get('org_type', 'production')
447
+
448
+ # Extract code from URL if full URL was provided
449
+ auth_code = auth_code_or_url.strip()
450
+
451
+ if 'login.salesforce.com' in auth_code or 'test.salesforce.com' in auth_code:
452
+ # Full URL provided - extract code
453
+ try:
454
+ parsed_url = urlparse(auth_code)
455
+ query_params = parse_qs(parsed_url.query)
456
+
457
+ if 'code' in query_params:
458
+ auth_code = query_params['code'][0]
459
+ logger.info("✅ Extracted code from URL")
460
+ else:
461
+ error_msg = "❌ No authorization code found in URL"
462
+ logger.error(error_msg)
463
+ return f"<div style='color: red; padding: 20px;'>{error_msg}</div>", json.dumps({"error": error_msg}, indent=2), None
464
+ except Exception as e:
465
+ error_msg = f"❌ Failed to parse URL: {str(e)}"
466
+ logger.error(error_msg)
467
+ return f"<div style='color: red; padding: 20px;'>{error_msg}</div>", json.dumps({"error": error_msg}, indent=2), None
468
+
469
+ if not auth_code:
470
+ error_msg = "❌ No authorization code provided"
471
+ logger.error(error_msg)
472
+ return f"<div style='color: red; padding: 20px;'>{error_msg}</div>", json.dumps({"error": error_msg}, indent=2), None
473
+
474
+ try:
475
+ # Exchange code for token
476
+ token_data = exchange_code_for_token(auth_code, org_type)
477
+
478
+ if 'error' in token_data:
479
+ error_msg = f"❌ {token_data.get('error_description', 'Authentication failed')}"
480
+ logger.error(error_msg)
481
+ return f"<div style='color: red; padding: 20px;'>{error_msg}</div>", json.dumps(token_data, indent=2), None
482
+
483
+ # Connect to Salesforce
484
+ sf = Salesforce(
485
+ instance_url=token_data['instance_url'],
486
+ session_id=token_data['access_token']
487
+ )
488
+
489
+ # Extract metadata
490
+ metadata = extract_metadata(sf)
491
+
492
+ # Run analysis
493
+ analysis_results = analyze_org(metadata)
494
+
495
+ # Generate HTML report
496
+ report_path = generate_report(analysis_results, metadata)
497
+
498
+ # Read the report
499
+ with open(report_path, 'r') as f:
500
+ report_html = f.read()
501
+
502
+ # Clean up session
503
+ if session_id in OAUTH_SESSIONS:
504
+ del OAUTH_SESSIONS[session_id]
505
+
506
+ logger.info("✅ Scan complete!")
507
+
508
+ return report_html, json.dumps(analysis_results, indent=2, default=str), report_path
509
+
510
+ except Exception as e:
511
+ error_msg = f"❌ Analysis failed: {str(e)}"
512
+ logger.error(error_msg)
513
+ logger.exception("Full error:")
514
+ return f"<div style='color: red; padding: 20px;'>{error_msg}</div>", json.dumps({"error": str(e)}, indent=2), None
515
+
516
+ # ============================================================================
517
+ # JAVASCRIPT AUTO CODE EXTRACTION
518
+ # ============================================================================
519
+
520
+ AUTO_EXTRACT_JS = """
521
+ <script>
522
+ // Auto-extract authorization code from URL
523
+ function autoExtractCode() {
524
+ // Check if we're on the success page
525
+ const currentUrl = window.location.href;
526
+
527
+ // Look for code in URL parameters
528
+ const urlParams = new URLSearchParams(window.location.search);
529
+ const code = urlParams.get('code');
530
+ const state = urlParams.get('state');
531
+
532
+ if (code && state) {
533
+ // Send code back to parent window
534
+ if (window.opener) {
535
+ window.opener.postMessage({
536
+ type: 'SALESFORCE_AUTH_CODE',
537
+ code: code,
538
+ state: state
539
+ }, '*');
540
+
541
+ // Close this window
542
+ setTimeout(() => {
543
+ window.close();
544
+ }, 1000);
545
+ }
546
+ }
547
+ }
548
+
549
+ // Listen for auth codes from popup windows
550
+ window.addEventListener('message', function(event) {
551
+ // Verify message is from expected source
552
+ if (event.data.type === 'SALESFORCE_AUTH_CODE') {
553
+ const code = event.data.code;
554
+ const state = event.data.state;
555
+
556
+ // Find the session ID input and auth code input
557
+ const sessionInput = document.querySelector('input[label="📋 Session ID (Auto-filled)"]');
558
+ const codeInput = document.querySelector('textarea[label="🔑 Authorization Code"]');
559
+
560
+ if (codeInput && code) {
561
+ // Auto-fill the code
562
+ codeInput.value = code;
563
+
564
+ // Trigger input event so Gradio detects the change
565
+ const inputEvent = new Event('input', { bubbles: true });
566
+ codeInput.dispatchEvent(inputEvent);
567
+
568
+ // Auto-click the scan button after a short delay
569
+ setTimeout(() => {
570
+ const scanButton = document.querySelector('button:contains("Start AI-Powered Security Scan")');
571
+ if (scanButton) {
572
+ scanButton.click();
573
+ }
574
+ }, 500);
575
+
576
+ console.log('✅ Authorization code auto-extracted and scan started!');
577
+ }
578
+ }
579
+ });
580
+
581
+ // Run on page load
582
+ autoExtractCode();
583
+ </script>
584
+ """
585
+
586
+ # ============================================================================
587
+ # GRADIO INTERFACE WITH AUTO EXTRACTION
588
+ # ============================================================================
589
+
590
+ ai_status = "✅ Active" if (ai_analyzer and ai_analyzer.available) else "⚠️ Not Available"
591
+
592
+ with gr.Blocks(
593
+ title="Optimax Security Agent",
594
+ theme=gr.themes.Soft(primary_hue="purple"),
595
+ head=AUTO_EXTRACT_JS # Inject JavaScript for auto-extraction
596
+ ) as demo:
597
+
598
+ gr.Markdown("""
599
+ # 🛡️ Optimax Security Agent
600
+
601
+ ## Multi-Model AI-Powered Security Scanner
602
+
603
+ **Enhanced with Automatic Code Detection! 🎉**
604
+ """)
605
+
606
+ with gr.Tab("🔍 Scan Your Org"):
607
+ gr.Markdown("""
608
+ ### Connect Your Salesforce Org
609
+
610
+ Advanced AI-powered security analysis using 4 specialized models.
611
+
612
+ 🔒 **Your credentials stay in Salesforce · We only read metadata · AI + Rule-based analysis**
613
+
614
+ ✨ **NEW: Authorization code is automatically detected from the URL!**
615
+ """)
616
+
617
+ with gr.Row():
618
+ prod_button = gr.Button(
619
+ "🏢 Production / Developer Org",
620
+ variant="primary",
621
+ size="lg",
622
+ scale=1
623
+ )
624
+ sandbox_button = gr.Button(
625
+ "🧪 Sandbox Org",
626
+ variant="secondary",
627
+ size="lg",
628
+ scale=1
629
+ )
630
+
631
+ instructions = gr.Markdown("")
632
+ oauth_url_hidden = gr.Textbox(visible=False)
633
+
634
+ # Authorization link - opens in popup for auto-extraction
635
+ auth_link = gr.HTML("")
636
+
637
+ gr.Markdown("---")
638
+
639
+ with gr.Row():
640
+ session_id = gr.Textbox(
641
+ label="📋 Session ID (Auto-filled)",
642
+ interactive=False,
643
+ scale=1
644
+ )
645
+ auth_code = gr.Textbox(
646
+ label="🔑 Authorization Code (Auto-detected)",
647
+ placeholder="Will be automatically filled from the authorization popup...",
648
+ lines=2,
649
+ scale=2
650
+ )
651
+
652
+ scan_button = gr.Button(
653
+ "🚀 Start AI-Powered Security Scan",
654
+ variant="primary",
655
+ size="lg"
656
+ )
657
+
658
+ gr.Markdown("""
659
+ 💡 **How it works:**
660
+ 1. Click "Connect" → Opens authorization in new window
661
+ 2. Authorize in Salesforce
662
+ 3. Code is **automatically extracted** and scan starts! ✨
663
+
664
+ If automatic detection fails, you can still paste the code manually.
665
+ """)
666
+
667
+ gr.Markdown("---\n### 📊 Security Analysis Results")
668
+
669
+ # Download Button
670
+ download_button = gr.File(
671
+ label="📥 Download Complete HTML Report",
672
+ visible=True,
673
+ interactive=False
674
+ )
675
+
676
+ # HTML Report Display
677
+ report_html = gr.HTML(
678
+ label="Security Report",
679
+ value="<div style='text-align: center; padding: 40px; color: #6b7280;'>Your AI-powered security analysis will appear here...</div>"
680
+ )
681
+
682
+ # Raw JSON
683
+ with gr.Accordion("🔍 View Raw JSON Data", open=False):
684
+ results = gr.Textbox(
685
+ label="Analysis Report (JSON)",
686
+ lines=15,
687
+ placeholder="Raw JSON data..."
688
+ )
689
+
690
+ # Event handlers
691
+ def show_oauth_link(sid, url, inst):
692
+ if url:
693
+ # Open in popup window for auto-extraction
694
+ link_html = f'''
695
+ <div style="text-align: center; margin: 30px 0;">
696
+ <a href="{url}" target="_blank" onclick="window.open(this.href, 'SalesforceAuth', 'width=600,height=700'); return false;" style="display: inline-block; padding: 15px 40px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; text-decoration: none; border-radius: 10px; font-size: 18px; font-weight: bold; box-shadow: 0 4px 6px rgba(0,0,0,0.2);">
697
+ 🚀 Open Salesforce to Authorize
698
+ </a>
699
+ <p style="margin-top: 15px; color: #666; font-size: 14px;">
700
+ ✨ Authorization code will be automatically detected!
701
+ </p>
702
+ </div>
703
+ '''
704
+ return link_html
705
+ return ""
706
+
707
+ prod_button.click(
708
+ fn=connect_production_org,
709
+ outputs=[session_id, oauth_url_hidden, instructions]
710
+ ).then(
711
+ fn=show_oauth_link,
712
+ inputs=[session_id, oauth_url_hidden, instructions],
713
+ outputs=[auth_link]
714
+ )
715
+
716
+ sandbox_button.click(
717
+ fn=connect_sandbox_org,
718
+ outputs=[session_id, oauth_url_hidden, instructions]
719
+ ).then(
720
+ fn=show_oauth_link,
721
+ inputs=[session_id, oauth_url_hidden, instructions],
722
+ outputs=[auth_link]
723
+ )
724
+
725
+ scan_button.click(
726
+ fn=scan_salesforce_org,
727
+ inputs=[session_id, auth_code],
728
+ outputs=[report_html, results, download_button]
729
+ )
730
+
731
+ with gr.Tab("ℹ️ About"):
732
+ ai_models = ["CodeBERT (125M)", "RoBERTa (125M)", "SecBERT (110M)", "Isolation Forest"] if (ai_analyzer and ai_analyzer.available) else []
733
+
734
+ gr.Markdown(f"""
735
+ ## Optimax Security Agent
736
+
737
+ ### 🤖 AI Models
738
+ **Status:** {ai_status}
739
+
740
+ {'**Active Models:**' if ai_models else '**AI Models:** Not loaded'}
741
+ {chr(10).join(['- ' + model for model in ai_models]) if ai_models else ''}
742
+
743
+ ### 🎯 Features
744
+ - 🤖 Multi-Model AI Analysis (4 specialized models)
745
+ - ✨ **Automatic Code Detection** (JavaScript-based)
746
+ - 📋 Rule-Based Vulnerability Detection
747
+ - 🔍 Permission & Access Analysis
748
+ - 👤 Identity Management Review
749
+ - 🔐 Sharing Model Assessment
750
+ - 🚨 Behavioral Anomaly Detection
751
+ - 📊 Hybrid Risk Scoring (AI + Rules)
752
+ - 💡 Actionable Recommendations
753
+
754
+ ### 🔒 Security & Privacy
755
+ - ✅ OAuth 2.0 authentication
756
+ - ✅ Automatic code extraction (browser-side)
757
+ - ✅ Metadata-only scanning
758
+ - ✅ Zero credential storage
759
+ - ✅ No data persistence
760
+
761
+ ### 📖 How It Works
762
+ 1. Click "Connect" → Opens popup
763
+ 2. Authorize in Salesforce
764
+ 3. JavaScript auto-detects code from URL
765
+ 4. Code auto-fills and scan starts
766
+ 5. View comprehensive results
767
+
768
+ **Version:** 3.3.0 (Auto Code Detection)
769
+ **Last Updated:** January 2026
770
+ """)
771
+
772
+ if __name__ == "__main__":
773
+ logger.info("")
774
+ logger.info("=" * 80)
775
+ logger.info("🌐 STARTING GRADIO INTERFACE")
776
+ logger.info("=" * 80)
777
+ logger.info(f" AI Models: {ai_status}")
778
+ logger.info(f" OAuth: ✅ Configured")
779
+ logger.info(f" Mode: Auto code detection (JavaScript)")
780
+ logger.info("=" * 80)
781
+ logger.info("")
782
+
783
+ demo.launch(
784
+ server_name="0.0.0.0",
785
+ server_port=7860,
786
+ show_error=True
787
+ )
identity_analyzer.py ADDED
@@ -0,0 +1,216 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # analyzers/identity_analyzer.py
2
+ from datetime import datetime, timedelta
3
+ from typing import Dict, List, Optional
4
+
5
+ class IdentityAnalyzer:
6
+ """
7
+ Analyzes identity and access management for security issues
8
+ """
9
+
10
+ def analyze_users(self, users: List[Dict], login_history: List[Dict]) -> Dict:
11
+ """
12
+ Analyze user accounts for security issues
13
+
14
+ Args:
15
+ users: List of user records
16
+ login_history: Login history records
17
+
18
+ Returns:
19
+ Analysis results with findings
20
+ """
21
+ findings = []
22
+
23
+ # Analyze dormant users
24
+ dormant_findings = self._find_dormant_users(users)
25
+ findings.extend(dormant_findings)
26
+
27
+ # Analyze admin users
28
+ admin_findings = self._analyze_admin_users(users)
29
+ findings.extend(admin_findings)
30
+
31
+ # Analyze MFA compliance
32
+ mfa_findings = self._analyze_mfa_compliance(users)
33
+ findings.extend(mfa_findings)
34
+
35
+ # Analyze login anomalies
36
+ login_findings = self._analyze_login_history(login_history)
37
+ findings.extend(login_findings)
38
+
39
+ return {
40
+ 'findings': findings,
41
+ 'total_users': len(users),
42
+ 'active_users': len([u for u in users if u.get('IsActive')]),
43
+ 'admin_users': len([u for u in users if 'Admin' in u.get('Profile', {}).get('Name', '')])
44
+ }
45
+
46
+ def _find_dormant_users(self, users: List[Dict]) -> List[Dict]:
47
+ """
48
+ Find users who haven't logged in recently
49
+ """
50
+ findings = []
51
+ threshold_days = 90
52
+ threshold_date = datetime.now() - timedelta(days=threshold_days)
53
+
54
+ for user in users:
55
+ last_login = user.get('LastLoginDate')
56
+
57
+ if not last_login:
58
+ findings.append({
59
+ 'type': 'Dormant User - Never Logged In',
60
+ 'severity': 'Medium',
61
+ 'username': user.get('Username'),
62
+ 'user_id': user.get('Id'),
63
+ 'name': user.get('Name', 'Unknown'), # ADDED
64
+ 'email': user.get('Email', 'N/A'), # ADDED
65
+ 'status': 'Active' if user.get('IsActive') else 'Inactive', # ADDED
66
+ 'profile': user.get('Profile', {}).get('Name', 'Unknown'),
67
+ 'description': f"User {user.get('Username')} has never logged in",
68
+ 'recommendation': 'Deactivate or review necessity of this account'
69
+ })
70
+ elif isinstance(last_login, str):
71
+ try:
72
+ last_login_date = datetime.fromisoformat(last_login.replace('Z', '+00:00'))
73
+ if last_login_date < threshold_date:
74
+ days_inactive = (datetime.now() - last_login_date).days
75
+ findings.append({
76
+ 'type': 'Dormant User - Inactive',
77
+ 'severity': 'Medium',
78
+ 'username': user.get('Username'),
79
+ 'user_id': user.get('Id'),
80
+ 'name': user.get('Name', 'Unknown'), # ADDED
81
+ 'email': user.get('Email', 'N/A'), # ADDED
82
+ 'status': 'Active' if user.get('IsActive') else 'Inactive', # ADDED
83
+ 'profile': user.get('Profile', {}).get('Name', 'Unknown'),
84
+ 'days_inactive': days_inactive,
85
+ 'last_login': last_login,
86
+ 'description': f"User inactive for {days_inactive} days",
87
+ 'recommendation': 'Consider deactivating inactive accounts'
88
+ })
89
+ except:
90
+ pass
91
+
92
+ return findings
93
+
94
+ def _analyze_admin_users(self, users: List[Dict]) -> List[Dict]:
95
+ """
96
+ Analyze administrative users for security risks
97
+ """
98
+ findings = []
99
+
100
+ admin_users = [
101
+ u for u in users
102
+ if 'Admin' in u.get('Profile', {}).get('Name', '') or
103
+ 'System Administrator' in u.get('Profile', {}).get('Name', '')
104
+ ]
105
+
106
+ if len(admin_users) > 5:
107
+ # Create detailed user list with name, email, status
108
+ admin_details = []
109
+ for u in admin_users:
110
+ admin_details.append({
111
+ 'username': u.get('Username'),
112
+ 'name': u.get('Name', 'Unknown'),
113
+ 'email': u.get('Email', 'N/A'),
114
+ 'status': 'Active' if u.get('IsActive') else 'Inactive'
115
+ })
116
+
117
+ findings.append({
118
+ 'type': 'Excessive Admin Users',
119
+ 'severity': 'High',
120
+ 'admin_count': len(admin_users),
121
+ 'description': f"{len(admin_users)} users have System Administrator profile",
122
+ 'impact': 'Too many users with full org access increases security risk',
123
+ 'recommendation': 'Reduce admin users, use Permission Sets for specific needs',
124
+ 'admin_usernames': [u.get('Username') for u in admin_users],
125
+ 'admin_details': admin_details # ADDED: Detailed user info
126
+ })
127
+
128
+ return findings
129
+
130
+ def _analyze_mfa_compliance(self, users: List[Dict]) -> List[Dict]:
131
+ """
132
+ Analyze Multi-Factor Authentication compliance
133
+ """
134
+ findings = []
135
+
136
+ # Check for users without MFA (if field exists)
137
+ non_mfa_users = []
138
+ non_mfa_details = [] # ADDED
139
+
140
+ for user in users:
141
+ # Note: MfaEnabled__c might be a custom field
142
+ if user.get('MfaEnabled__c') == False:
143
+ non_mfa_users.append(user.get('Username'))
144
+ # ADDED: Collect detailed user info
145
+ non_mfa_details.append({
146
+ 'username': user.get('Username'),
147
+ 'name': user.get('Name', 'Unknown'),
148
+ 'email': user.get('Email', 'N/A'),
149
+ 'status': 'Active' if user.get('IsActive') else 'Inactive',
150
+ 'profile': user.get('Profile', {}).get('Name', 'Unknown')
151
+ })
152
+
153
+ if non_mfa_users:
154
+ findings.append({
155
+ 'type': 'MFA Not Enabled',
156
+ 'severity': 'High',
157
+ 'users_without_mfa': len(non_mfa_users),
158
+ 'description': f"{len(non_mfa_users)} users don't have MFA enabled",
159
+ 'impact': 'Accounts vulnerable to credential compromise',
160
+ 'recommendation': 'Enable MFA for all users, especially admins',
161
+ 'affected_users': non_mfa_users[:10], # Show first 10 usernames
162
+ 'user_details': non_mfa_details[:10] # ADDED: First 10 with full details
163
+ })
164
+
165
+ return findings
166
+
167
+ def _analyze_login_history(self, login_history: List[Dict]) -> List[Dict]:
168
+ """
169
+ Analyze login history for suspicious patterns
170
+ """
171
+ findings = []
172
+
173
+ # Count failed login attempts
174
+ failed_logins = [l for l in login_history if l.get('Status') == 'Failed']
175
+
176
+ if len(failed_logins) > 100:
177
+ findings.append({
178
+ 'type': 'High Failed Login Attempts',
179
+ 'severity': 'High',
180
+ 'failed_count': len(failed_logins),
181
+ 'description': f"{len(failed_logins)} failed login attempts detected",
182
+ 'impact': 'Possible brute force attack or credential stuffing',
183
+ 'recommendation': 'Review failed attempts, consider IP restrictions'
184
+ })
185
+
186
+ # Check for logins from unusual locations (simplified)
187
+ unique_ips = set(l.get('SourceIp', '') for l in login_history if l.get('SourceIp'))
188
+
189
+ if len(unique_ips) > 100:
190
+ findings.append({
191
+ 'type': 'Many Unique Login IPs',
192
+ 'severity': 'Medium',
193
+ 'unique_ip_count': len(unique_ips),
194
+ 'description': f"Logins from {len(unique_ips)} different IP addresses",
195
+ 'recommendation': 'Review for unauthorized access, implement IP restrictions'
196
+ })
197
+
198
+ return findings
199
+
200
+ def check_mfa_compliance(self, users: List[Dict]) -> Dict:
201
+ """
202
+ Check organization-wide MFA compliance
203
+
204
+ Returns:
205
+ MFA compliance statistics
206
+ """
207
+ total_users = len(users)
208
+ mfa_enabled = sum(1 for u in users if u.get('MfaEnabled__c') == True)
209
+
210
+ return {
211
+ 'total_users': total_users,
212
+ 'mfa_enabled': mfa_enabled,
213
+ 'mfa_disabled': total_users - mfa_enabled,
214
+ 'compliance_percentage': (mfa_enabled / total_users * 100) if total_users > 0 else 0,
215
+ 'is_compliant': (mfa_enabled / total_users) >= 0.95 if total_users > 0 else False
216
+ }
optimax_reports.py ADDED
@@ -0,0 +1,734 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Optimax Report Generator - Category-Based Security Analysis
3
+ Organizes findings into 6 security categories WITHOUT mentioning AI models:
4
+
5
+ 1. Profiles & Permissions
6
+ 2. Sharing Model
7
+ 3. API & Integration
8
+ 4. Guest/Public Exposure
9
+ 5. Custom Code
10
+ 6. Identity & Sessions
11
+
12
+ Usage:
13
+ from optimax_reports import generate_report
14
+ html_report = generate_report(analysis_result)
15
+ """
16
+
17
+ def generate_report(analysis_result: dict) -> str:
18
+ """Generate comprehensive HTML report organized by security categories"""
19
+
20
+ if not analysis_result.get('success', False):
21
+ return _generate_error_html(analysis_result.get('error', 'Unknown error'))
22
+
23
+ # Extract data
24
+ org_name = analysis_result.get('org_name', 'Unknown Organization')
25
+ org_id = analysis_result.get('org_id', 'N/A')
26
+ timestamp = analysis_result.get('timestamp', '')
27
+ risk_score = analysis_result.get('overall_risk_score', 0)
28
+ risk_level = analysis_result.get('risk_level', 'UNKNOWN')
29
+
30
+ # Get all findings
31
+ all_findings = (
32
+ analysis_result.get('critical_findings', []) +
33
+ analysis_result.get('high_findings', []) +
34
+ analysis_result.get('medium_findings', []) +
35
+ analysis_result.get('low_findings', [])
36
+ )
37
+
38
+ ai_summary = analysis_result.get('ai_executive_summary', '')
39
+ ai_recommendations = analysis_result.get('ai_recommendations', [])
40
+ ai_detailed = analysis_result.get('ai_detailed_results', {})
41
+ statistics = analysis_result.get('statistics', {})
42
+
43
+ # Categorize all findings
44
+ categorized_findings = _categorize_findings(all_findings, ai_detailed)
45
+
46
+ risk_colors = {
47
+ 'CRITICAL': '#dc2626',
48
+ 'HIGH': '#ea580c',
49
+ 'MEDIUM': '#f59e0b',
50
+ 'LOW': '#10b981'
51
+ }
52
+ risk_color = risk_colors.get(risk_level, '#6b7280')
53
+
54
+ # Build comprehensive HTML with FIXED download button
55
+ html = f"""
56
+ <style>
57
+ .optimax-report * {{ margin: 0; padding: 0; box-sizing: border-box; }}
58
+ .optimax-report {{ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif; line-height: 1.6; color: #000000 !important; background: #ffffff !important; border-radius: 12px; overflow: hidden; }}
59
+ .optimax-header {{ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: #ffffff !important; padding: 40px; text-align: center; }}
60
+ .optimax-header h1 {{ font-size: 2.5em; margin-bottom: 10px; font-weight: 700; color: #ffffff !important; }}
61
+ .optimax-header .subtitle {{ font-size: 1.2em; opacity: 1; color: #ffffff !important; margin-top: 10px; }}
62
+ .optimax-content {{ padding: 40px; background: #ffffff !important; }}
63
+ .optimax-section {{ margin-bottom: 40px; }}
64
+ .optimax-section-title {{ font-size: 1.8em; font-weight: 700; margin-bottom: 20px; color: #000000 !important; border-bottom: 3px solid #667eea; padding-bottom: 10px; }}
65
+ .optimax-category-title {{ font-size: 1.4em; font-weight: 700; margin: 30px 0 15px 0; color: #000000 !important; display: flex; align-items: center; gap: 10px; }}
66
+ .optimax-risk-score-box {{ text-align: center; padding: 30px; background: #f9fafb !important; border-radius: 10px; margin-bottom: 30px; border: 1px solid #e5e7eb; }}
67
+ .optimax-risk-score {{ font-size: 4em; font-weight: 700; color: {risk_color} !important; margin: 10px 0; }}
68
+ .optimax-risk-level {{ font-size: 1.3em; font-weight: 700; color: {risk_color} !important; text-transform: uppercase; letter-spacing: 0.1em; }}
69
+ .optimax-risk-cards {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin-bottom: 40px; }}
70
+ .optimax-risk-card {{ background: #ffffff !important; border-radius: 10px; padding: 25px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); border-left: 5px solid; }}
71
+ .optimax-risk-card.critical {{ border-left-color: #dc2626; }}
72
+ .optimax-risk-card.high {{ border-left-color: #ea580c; }}
73
+ .optimax-risk-card.medium {{ border-left-color: #f59e0b; }}
74
+ .optimax-risk-card.low {{ border-left-color: #10b981; }}
75
+ .optimax-risk-card-title {{ font-size: 0.9em; text-transform: uppercase; color: #4b5563 !important; margin-bottom: 10px; font-weight: 700; }}
76
+ .optimax-risk-card-value {{ font-size: 2.5em; font-weight: 700; color: #000000 !important; text-shadow: 0 1px 2px rgba(0,0,0,0.1); }}
77
+ .optimax-stats-grid {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 15px; margin-bottom: 30px; }}
78
+ .optimax-stat-item {{ background: #f9fafb !important; padding: 20px; border-radius: 8px; border-left: 4px solid #667eea; }}
79
+ .optimax-stat-label {{ font-size: 0.85em; color: #4b5563 !important; text-transform: uppercase; font-weight: 700; margin-bottom: 5px; }}
80
+ .optimax-stat-value {{ font-size: 1.6em; font-weight: 700; color: #000000 !important; }}
81
+ .optimax-finding-card {{ background: #f8fafc !important; border: 2px solid #e2e8f0; border-radius: 8px; padding: 20px; margin: 15px 0; }}
82
+ .optimax-finding-header {{ display: flex; align-items: center; gap: 12px; margin-bottom: 12px; flex-wrap: wrap; }}
83
+ .optimax-finding-title {{ font-weight: 700; color: #000000 !important; font-size: 1.05em; flex: 1; min-width: 200px; }}
84
+ .optimax-finding-body {{ color: #000000 !important; margin: 10px 0; line-height: 1.7; }}
85
+ .optimax-finding-body strong {{ color: #000000 !important; font-weight: 700; }}
86
+ .optimax-severity-badge {{ display: inline-block !important; padding: 6px 14px !important; border-radius: 20px !important; font-size: 0.85em !important; font-weight: 700 !important; text-transform: uppercase !important; letter-spacing: 0.05em !important; white-space: nowrap !important; }}
87
+ .optimax-severity-critical {{ background: #fee2e2 !important; color: #991b1b !important; border: 2px solid #991b1b !important; }}
88
+ .optimax-severity-high {{ background: #ffedd5 !important; color: #9a3412 !important; border: 2px solid #9a3412 !important; }}
89
+ .optimax-severity-medium {{ background: #fef3c7 !important; color: #92400e !important; border: 2px solid #92400e !important; }}
90
+ .optimax-severity-low {{ background: #d1fae5 !important; color: #065f46 !important; border: 2px solid #065f46 !important; }}
91
+ .optimax-summary-box {{ background: #e0f2fe; padding: 25px; border-radius: 10px; margin-bottom: 30px; border-left: 5px solid #0ea5e9; }}
92
+ .optimax-summary-title {{ font-size: 1.3em; font-weight: 700; color: #0c4a6e; margin-bottom: 15px; }}
93
+ .optimax-summary-box p {{ color: #000000 !important; line-height: 1.7; }}
94
+ .optimax-rec-box {{ background: #f0fdf4; padding: 25px; border-radius: 10px; border-left: 5px solid #10b981; }}
95
+ .optimax-rec-title {{ font-size: 1.3em; font-weight: 700; color: #065f46; margin-bottom: 15px; }}
96
+ .optimax-rec-list {{ list-style: none; padding-left: 0; }}
97
+ .optimax-rec-list li {{ padding: 12px 0; padding-left: 30px; position: relative; border-bottom: 1px solid #d1fae5; color: #000000 !important; line-height: 1.6; }}
98
+ .optimax-rec-list li:last-child {{ border-bottom: none; }}
99
+ .optimax-rec-list li::before {{ content: "✓"; position: absolute; left: 0; color: #10b981; font-weight: 700; font-size: 1.2em; }}
100
+ .optimax-no-findings {{ text-align: center; padding: 40px; color: #4b5563 !important; font-style: italic; }}
101
+ .optimax-category-icon {{ font-size: 1.3em; }}
102
+ .optimax-metric-grid {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 10px; margin: 10px 0; }}
103
+ .optimax-metric-item {{ background: #f9fafb !important; padding: 10px 15px; border-radius: 6px; border-left: 3px solid #667eea; }}
104
+ .optimax-metric-label {{ font-size: 0.75em; color: #4b5563 !important; text-transform: uppercase; font-weight: 700; }}
105
+ .optimax-metric-value {{ font-size: 1.1em; font-weight: 700; color: #000000 !important; margin-top: 3px; }}
106
+ .optimax-recommendation {{ background: #dcfce7; border-left: 4px solid #10b981; padding: 12px 15px; border-radius: 6px; margin: 10px 0; }}
107
+ .optimax-recommendation-title {{ font-weight: 700; color: #166534; font-size: 0.95em; }}
108
+ .optimax-recommendation-text {{ color: #166534; margin-top: 5px; font-size: 0.9em; line-height: 1.5; }}
109
+ .optimax-affected-users {{ background: #fef2f2; padding: 10px 15px; border-radius: 6px; margin: 10px 0; border-left: 3px solid #dc2626; }}
110
+ .optimax-affected-users-title {{ font-weight: 700; color: #991b1b; font-size: 0.9em; }}
111
+ .optimax-affected-users-list {{ color: #991b1b; font-size: 0.85em; margin-top: 5px; }}
112
+ .optimax-category-summary {{ background: #f9fafb !important; padding: 15px 20px; border-radius: 8px; margin: 15px 0 25px 0; border-left: 4px solid #667eea; }}
113
+ .optimax-category-summary-text {{ color: #000000 !important; font-size: 0.95em; line-height: 1.6; }}
114
+ .optimax-download-btn {{ display: inline-block; padding: 12px 30px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: #ffffff !important; text-decoration: none; border-radius: 8px; font-weight: 700; font-size: 1em; cursor: pointer; border: none; box-shadow: 0 4px 6px rgba(0,0,0,0.1); transition: all 0.3s ease; margin: 10px 0; }}
115
+ .optimax-download-btn:hover {{ transform: translateY(-2px); box-shadow: 0 6px 12px rgba(0,0,0,0.15); }}
116
+ .optimax-download-btn:active {{ transform: translateY(0); }}
117
+ </style>
118
+
119
+ <script>
120
+ function downloadReport() {{
121
+ try {{
122
+ // Wait a moment for Gradio to fully render
123
+ setTimeout(() => {{
124
+ // Find the report container
125
+ let reportContainer = document.querySelector('.optimax-report');
126
+
127
+ // If not found in current context, try looking in parent frames
128
+ if (!reportContainer && window.parent) {{
129
+ reportContainer = window.parent.document.querySelector('.optimax-report');
130
+ }}
131
+
132
+ if (!reportContainer) {{
133
+ // Last resort: get the entire HTML content
134
+ const htmlContent = document.documentElement.outerHTML;
135
+ const blob = new Blob([htmlContent], {{ type: 'text/html' }});
136
+ const url = URL.createObjectURL(blob);
137
+ const link = document.createElement('a');
138
+ const timestamp = new Date().toISOString().slice(0, 19).replace(/:/g, '-');
139
+ link.href = url;
140
+ link.download = `Optimax_Report_${{timestamp}}.html`;
141
+ link.click();
142
+ URL.revokeObjectURL(url);
143
+ alert('✅ Report downloaded!');
144
+ return;
145
+ }}
146
+
147
+ // Clone and prepare report
148
+ const clone = reportContainer.cloneNode(true);
149
+ const downloadBtn = clone.querySelector('.optimax-download-btn');
150
+ if (downloadBtn) downloadBtn.remove();
151
+
152
+ // Get styles
153
+ const styles = Array.from(document.querySelectorAll('style'))
154
+ .map(s => s.innerHTML).join('\\n');
155
+
156
+ // Create complete HTML
157
+ const timestamp = new Date().toISOString().slice(0, 19).replace(/:/g, '-');
158
+ const fullHTML = `<!DOCTYPE html>
159
+ <html>
160
+ <head>
161
+ <meta charset="UTF-8">
162
+ <title>Optimax Security Report</title>
163
+ <style>${{styles}}</style>
164
+ </head>
165
+ <body style="margin:0;padding:20px;background:#f9fafb">
166
+ ${{clone.outerHTML}}
167
+ </body>
168
+ </html>`;
169
+
170
+ // Download
171
+ const blob = new Blob([fullHTML], {{ type: 'text/html' }});
172
+ const url = URL.createObjectURL(blob);
173
+ const link = document.createElement('a');
174
+ link.href = url;
175
+ link.download = `Optimax_Security_Report_${{timestamp}}.html`;
176
+ document.body.appendChild(link);
177
+ link.click();
178
+ document.body.removeChild(link);
179
+ URL.revokeObjectURL(url);
180
+
181
+ // Visual feedback
182
+ const btn = document.querySelector('.optimax-download-btn');
183
+ if (btn) {{
184
+ const orig = btn.innerHTML;
185
+ btn.innerHTML = '✅ Downloaded!';
186
+ btn.style.background = '#10b981';
187
+ setTimeout(() => {{
188
+ btn.innerHTML = orig;
189
+ btn.style.background = 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)';
190
+ }}, 2000);
191
+ }}
192
+ }}, 100);
193
+ }} catch (error) {{
194
+ console.error('Download error:', error);
195
+ alert('Download failed: ' + error.message + '\\n\\nTry right-clicking and Save As instead.');
196
+ }}
197
+ }}
198
+ </script>
199
+
200
+ <div class="optimax-report">
201
+ <div class="optimax-header">
202
+ <h1>🛡️ Optimax Security Report</h1>
203
+ <div class="subtitle">Comprehensive Security Analysis</div>
204
+ <button class="optimax-download-btn" onclick="downloadReport()" type="button">
205
+ 📥 Download Report
206
+ </button>
207
+ </div>
208
+
209
+ <div class="optimax-content">
210
+ <!-- Organization Info -->
211
+ <div class="optimax-section">
212
+ <div class="optimax-stats-grid">
213
+ <div class="optimax-stat-item">
214
+ <div class="optimax-stat-label">Organization</div>
215
+ <div class="optimax-stat-value" style="font-size: 1.2em;">{org_name}</div>
216
+ </div>
217
+ <div class="optimax-stat-item">
218
+ <div class="optimax-stat-label">Org ID</div>
219
+ <div class="optimax-stat-value" style="font-size: 0.9em;">{org_id[:15]}...</div>
220
+ </div>
221
+ <div class="optimax-stat-item">
222
+ <div class="optimax-stat-label">Scan Date</div>
223
+ <div class="optimax-stat-value" style="font-size: 0.9em;">{timestamp[:10] if timestamp else 'N/A'}</div>
224
+ </div>
225
+ <div class="optimax-stat-item">
226
+ <div class="optimax-stat-label">Scan Time</div>
227
+ <div class="optimax-stat-value" style="font-size: 0.9em;">{timestamp[11:19] if timestamp else 'N/A'}</div>
228
+ </div>
229
+ </div>
230
+ </div>
231
+
232
+ <!-- Risk Assessment -->
233
+ <div class="optimax-section">
234
+ <h2 class="optimax-section-title">Overall Risk Assessment</h2>
235
+ <div class="optimax-risk-score-box">
236
+ <div style="color: #4b5563 !important; font-weight: 700; text-transform: uppercase; font-size: 0.9em;">Security Risk Score</div>
237
+ <div class="optimax-risk-score">{risk_score}/100</div>
238
+ <div class="optimax-risk-level">{risk_level} RISK</div>
239
+ </div>
240
+
241
+ <div class="optimax-risk-cards">
242
+ <div class="optimax-risk-card critical">
243
+ <div class="optimax-risk-card-title">Critical</div>
244
+ <div class="optimax-risk-card-value">{len(analysis_result.get('critical_findings', []))}</div>
245
+ </div>
246
+ <div class="optimax-risk-card high">
247
+ <div class="optimax-risk-card-title">High</div>
248
+ <div class="optimax-risk-card-value">{len(analysis_result.get('high_findings', []))}</div>
249
+ </div>
250
+ <div class="optimax-risk-card medium">
251
+ <div class="optimax-risk-card-title">Medium</div>
252
+ <div class="optimax-risk-card-value">{len(analysis_result.get('medium_findings', []))}</div>
253
+ </div>
254
+ <div class="optimax-risk-card low">
255
+ <div class="optimax-risk-card-title">Low</div>
256
+ <div class="optimax-risk-card-value">{len(analysis_result.get('low_findings', []))}</div>
257
+ </div>
258
+ </div>
259
+ </div>
260
+
261
+ <!-- Executive Summary -->
262
+ {_build_summary_section(ai_summary, ai_recommendations)}
263
+
264
+ <!-- Scan Statistics -->
265
+ <div class="optimax-section">
266
+ <h2 class="optimax-section-title">Scan Statistics</h2>
267
+ <div class="optimax-stats-grid">
268
+ {_build_stats(statistics)}
269
+ </div>
270
+ </div>
271
+
272
+ <!-- CATEGORY-BASED FINDINGS -->
273
+ <div class="optimax-section">
274
+ <h2 class="optimax-section-title">Security Findings by Category</h2>
275
+
276
+ {_build_category_section('Profiles & Permissions', categorized_findings['profiles_permissions'], '👤',
277
+ 'User profiles, permission sets, system permissions, and access controls')}
278
+
279
+ {_build_category_section('Sharing Model', categorized_findings['sharing_model'], '🔓',
280
+ 'Organization-wide defaults, sharing rules, role hierarchy, and record-level access')}
281
+
282
+ {_build_category_section('API & Integration', categorized_findings['api_integration'], '🔌',
283
+ 'API usage, connected apps, OAuth tokens, and external integrations')}
284
+
285
+ {_build_category_section('Guest/Public Exposure', categorized_findings['guest_public'], '🌐',
286
+ 'Guest user access, public pages, unauthenticated access, and site security')}
287
+
288
+ {_build_category_section('Custom Code', categorized_findings['custom_code'], '💻',
289
+ 'Apex classes, triggers, SOQL queries, and code security vulnerabilities')}
290
+
291
+ {_build_category_section('Identity & Sessions', categorized_findings['identity_sessions'], '🔐',
292
+ 'Login activity, MFA compliance, session security, and user authentication')}
293
+ </div>
294
+
295
+ </div>
296
+ </div>
297
+ """
298
+
299
+ return html
300
+
301
+
302
+ def _categorize_findings(all_findings: list, ai_detailed: dict) -> dict:
303
+ """Categorize findings into 6 security categories"""
304
+
305
+ categories = {
306
+ 'profiles_permissions': [],
307
+ 'sharing_model': [],
308
+ 'api_integration': [],
309
+ 'guest_public': [],
310
+ 'custom_code': [],
311
+ 'identity_sessions': []
312
+ }
313
+
314
+ # Categorize rule-based findings
315
+ for finding in all_findings:
316
+ finding_type = finding.get('type', '').lower()
317
+
318
+ # Profiles & Permissions
319
+ if any(keyword in finding_type for keyword in ['permission', 'profile', 'admin', 'privilege', 'escalation']):
320
+ categories['profiles_permissions'].append(finding)
321
+
322
+ # Sharing Model
323
+ elif any(keyword in finding_type for keyword in ['sharing', 'owd', 'role', 'hierarchy']):
324
+ categories['sharing_model'].append(finding)
325
+
326
+ # API & Integration
327
+ elif any(keyword in finding_type for keyword in ['api', 'integration', 'oauth', 'connected']):
328
+ categories['api_integration'].append(finding)
329
+
330
+ # Guest/Public Exposure
331
+ elif any(keyword in finding_type for keyword in ['guest', 'public', 'unauthenticated', 'site']):
332
+ categories['guest_public'].append(finding)
333
+
334
+ # Custom Code
335
+ elif any(keyword in finding_type for keyword in ['code', 'apex', 'trigger', 'soql', 'class']):
336
+ categories['custom_code'].append(finding)
337
+
338
+ # Identity & Sessions
339
+ elif any(keyword in finding_type for keyword in ['login', 'mfa', 'dormant', 'session', 'password', 'authentication']):
340
+ categories['identity_sessions'].append(finding)
341
+
342
+ # Default to Profiles & Permissions if unclear
343
+ else:
344
+ categories['profiles_permissions'].append(finding)
345
+
346
+ # Add AI-detected findings if available
347
+ if ai_detailed and ai_detailed.get('available'):
348
+
349
+ # Code analysis findings -> Custom Code
350
+ for code_item in ai_detailed.get('code_analysis', []):
351
+ for vuln in code_item.get('vulnerabilities', []):
352
+ categories['custom_code'].append({
353
+ 'type': vuln.get('type', 'Code Vulnerability'),
354
+ 'severity': vuln.get('severity', 'Medium'),
355
+ 'description': vuln.get('description', 'Code security issue detected'),
356
+ 'class_name': code_item.get('class_name'),
357
+ 'confidence': vuln.get('confidence', 0),
358
+ 'recommendation': _get_code_recommendation(vuln.get('type', ''))
359
+ })
360
+
361
+ # Permission analysis -> Profiles & Permissions
362
+ for perm in ai_detailed.get('permission_analysis', []):
363
+ if perm.get('risk_level') in ['CRITICAL', 'HIGH']:
364
+ categories['profiles_permissions'].append({
365
+ 'type': f"{perm.get('type', 'Permission Set')} - {perm.get('name', 'Unknown')}",
366
+ 'severity': perm.get('risk_level', 'Medium'),
367
+ 'description': f"Risk level: {perm.get('risk_level')}",
368
+ 'dangerous_permissions': perm.get('dangerous_permissions', []),
369
+ 'confidence': perm.get('confidence', 0),
370
+ 'recommendation': 'Review and restrict dangerous permissions'
371
+ })
372
+
373
+ # Security analysis -> Identity & Sessions
374
+ for sec in ai_detailed.get('security_analysis', []):
375
+ categories['identity_sessions'].append({
376
+ 'type': sec.get('type', 'Security Policy Issue'),
377
+ 'severity': sec.get('severity', 'Medium'),
378
+ 'description': sec.get('description', 'Security policy violation detected'),
379
+ 'confidence': sec.get('confidence', 0),
380
+ 'recommendation': sec.get('recommendation', 'Review and remediate')
381
+ })
382
+
383
+ # Anomaly detection -> Identity & Sessions
384
+ for anomaly in ai_detailed.get('anomaly_analysis', []):
385
+ categories['identity_sessions'].append({
386
+ 'type': anomaly.get('type', 'Behavioral Anomaly'),
387
+ 'severity': anomaly.get('severity', 'Medium'),
388
+ 'description': anomaly.get('description', 'Unusual behavior detected'),
389
+ 'impact': anomaly.get('impact', ''),
390
+ 'recommendation': anomaly.get('recommendation', 'Investigate immediately'),
391
+ 'anomaly_score': anomaly.get('anomaly_score'),
392
+ 'username': anomaly.get('username'),
393
+ 'login_time': anomaly.get('login_time'),
394
+ 'source_ip': anomaly.get('source_ip')
395
+ })
396
+
397
+ return categories
398
+
399
+
400
+ def _get_code_recommendation(vuln_type: str) -> str:
401
+ """Get recommendation for code vulnerability"""
402
+ recommendations = {
403
+ 'SOQL_INJECTION': 'Use bind variables instead of string concatenation',
404
+ 'POTENTIAL_SOQL_INJECTION': 'Replace dynamic query with parameterized query',
405
+ 'MISSING_SHARING': 'Add with sharing keyword to class declaration',
406
+ 'POTENTIAL_HARDCODED_CREDENTIALS': 'Move credentials to Custom Metadata or Named Credentials',
407
+ 'UNSAFE_DML': 'Wrap DML operations in try-catch with proper error handling'
408
+ }
409
+ return recommendations.get(vuln_type, 'Review and remediate this security issue')
410
+
411
+
412
+ def _build_summary_section(ai_summary: str, ai_recommendations: list) -> str:
413
+ """Build executive summary section"""
414
+ if not ai_summary and not ai_recommendations:
415
+ return ''
416
+
417
+ html = '<div class="optimax-section">'
418
+
419
+ if ai_summary:
420
+ # Remove AI model mentions from summary
421
+ clean_summary = ai_summary
422
+ for model in ['CodeBERT', 'RoBERTa', 'SecBERT', 'Isolation Forest', 'AI-powered', 'AI analysis', 'AI models']:
423
+ clean_summary = clean_summary.replace(model, 'Security analysis')
424
+
425
+ html += f'''
426
+ <div class="optimax-summary-box">
427
+ <div class="optimax-summary-title" style="color: #0c4a6e !important;">📋 Executive Summary</div>
428
+ <p style="color: #000000 !important;">{clean_summary}</p>
429
+ </div>
430
+ '''
431
+
432
+ if ai_recommendations:
433
+ # Clean recommendations from AI model mentions
434
+ clean_recs = []
435
+ for rec in ai_recommendations:
436
+ clean_rec = rec
437
+ for model in ['CodeBERT', 'RoBERTa', 'SecBERT', 'Isolation Forest', 'AI-detected', 'AI analysis']:
438
+ clean_rec = clean_rec.replace(model, 'Analysis')
439
+ clean_recs.append(clean_rec)
440
+
441
+ html += '<div class="optimax-rec-box"><div class="optimax-rec-title" style="color: #065f46 !important;">💡 Key Recommendations</div><ul class="optimax-rec-list">'
442
+ for rec in clean_recs:
443
+ html += f'<li style="color: #000000 !important;">{rec}</li>'
444
+ html += '</ul></div>'
445
+
446
+ html += '</div>'
447
+ return html
448
+
449
+
450
+ def _build_stats(statistics: dict) -> str:
451
+ """Build statistics section"""
452
+ stats_items = [
453
+ ('Total Findings', statistics.get('total_findings', 0)),
454
+ ('Users Analyzed', statistics.get('users_analyzed', 0)),
455
+ ('Profiles Analyzed', statistics.get('profiles_analyzed', 0)),
456
+ ('Permission Sets', statistics.get('permission_sets_analyzed', 0)),
457
+ ('Login Records', statistics.get('login_records_analyzed', 0)),
458
+ ]
459
+
460
+ html = ''
461
+ for label, value in stats_items:
462
+ display_value = f'{value:,}' if isinstance(value, int) else str(value)
463
+ html += f'''
464
+ <div class="optimax-stat-item">
465
+ <div class="optimax-stat-label" style="color: #4b5563 !important;">{label}</div>
466
+ <div class="optimax-stat-value" style="color: #000000 !important;">{display_value}</div>
467
+ </div>
468
+ '''
469
+
470
+ return html
471
+
472
+
473
+ def _build_category_section(category_name: str, findings: list, icon: str, description: str) -> str:
474
+ """Build a category section with all findings"""
475
+
476
+ if not findings:
477
+ return f'''
478
+ <div class="optimax-category-title">
479
+ <span class="optimax-category-icon">{icon}</span>
480
+ <span style="color: #000000 !important;">{category_name}</span>
481
+ </div>
482
+ <div class="optimax-category-summary">
483
+ <div class="optimax-category-summary-text" style="color: #000000 !important;">
484
+ <em style="color: #000000 !important;">{description}</em>
485
+ </div>
486
+ </div>
487
+ <div class="optimax-no-findings" style="color: #4b5563 !important;">✅ No issues detected in this category</div>
488
+ '''
489
+
490
+ # Count by severity
491
+ critical = [f for f in findings if f.get('severity', '').upper() in ['CRITICAL']]
492
+ high = [f for f in findings if f.get('severity', '').upper() in ['HIGH']]
493
+ medium = [f for f in findings if f.get('severity', '').upper() in ['MEDIUM']]
494
+ low = [f for f in findings if f.get('severity', '').upper() in ['LOW']]
495
+
496
+ html = f'''
497
+ <div class="optimax-category-title">
498
+ <span class="optimax-category-icon">{icon}</span>
499
+ <span style="color: #000000 !important;">{category_name}</span>
500
+ </div>
501
+ <div class="optimax-category-summary">
502
+ <div class="optimax-category-summary-text" style="color: #000000 !important;">
503
+ <em style="color: #000000 !important;">{description}</em>
504
+ </div>
505
+ <div class="optimax-metric-grid" style="margin-top: 15px;">
506
+ <div class="optimax-metric-item">
507
+ <div class="optimax-metric-label" style="color: #4b5563 !important;">Critical</div>
508
+ <div class="optimax-metric-value" style="color: #dc2626 !important;">{len(critical)}</div>
509
+ </div>
510
+ <div class="optimax-metric-item">
511
+ <div class="optimax-metric-label" style="color: #4b5563 !important;">High</div>
512
+ <div class="optimax-metric-value" style="color: #ea580c !important;">{len(high)}</div>
513
+ </div>
514
+ <div class="optimax-metric-item">
515
+ <div class="optimax-metric-label" style="color: #4b5563 !important;">Medium</div>
516
+ <div class="optimax-metric-value" style="color: #f59e0b !important;">{len(medium)}</div>
517
+ </div>
518
+ <div class="optimax-metric-item">
519
+ <div class="optimax-metric-label" style="color: #4b5563 !important;">Low</div>
520
+ <div class="optimax-metric-value" style="color: #10b981 !important;">{len(low)}</div>
521
+ </div>
522
+ <div class="optimax-metric-item">
523
+ <div class="optimax-metric-label" style="color: #4b5563 !important;">Total</div>
524
+ <div class="optimax-metric-value" style="color: #000000 !important;">{len(findings)}</div>
525
+ </div>
526
+ </div>
527
+ </div>
528
+ '''
529
+
530
+ # Display findings
531
+ for finding in findings:
532
+ finding_type = finding.get('type', 'Unknown Issue')
533
+ severity = finding.get('severity', 'Medium').upper()
534
+ description = finding.get('description', 'No description available')
535
+ recommendation = finding.get('recommendation', 'Review and remediate')
536
+ impact = finding.get('impact', '')
537
+
538
+ # Build additional details
539
+ details_html = ''
540
+
541
+ if finding.get('class_name'):
542
+ details_html += f'<div style="color: #000000 !important;"><strong style="color: #000000 !important;">Apex Class:</strong> <span style="color: #000000 !important;">{finding["class_name"]}</span></div>'
543
+
544
+ if finding.get('dangerous_permissions'):
545
+ perms = finding['dangerous_permissions']
546
+ details_html += f'<div style="color: #000000 !important;"><strong style="color: #000000 !important;">Dangerous Permissions:</strong> <span style="color: #000000 !important;">{", ".join(perms)}</span></div>'
547
+
548
+ if finding.get('confidence'):
549
+ confidence_pct = int(finding['confidence'] * 100)
550
+ details_html += f'<div style="color: #000000 !important;"><strong style="color: #000000 !important;">Detection Confidence:</strong> <span style="color: #000000 !important;">{confidence_pct}%</span></div>'
551
+
552
+ if finding.get('username'):
553
+ details_html += f'<div style="color: #000000 !important;"><strong style="color: #000000 !important;">User:</strong> <span style="color: #000000 !important;">{finding["username"]}</span></div>'
554
+
555
+ if finding.get('login_time'):
556
+ details_html += f'<div style="color: #000000 !important;"><strong style="color: #000000 !important;">Time:</strong> <span style="color: #000000 !important;">{finding["login_time"]}</span></div>'
557
+
558
+ if finding.get('source_ip'):
559
+ details_html += f'<div style="color: #000000 !important;"><strong style="color: #000000 !important;">IP Address:</strong> <span style="color: #000000 !important;">{finding["source_ip"]}</span></div>'
560
+
561
+ if finding.get('days_inactive'):
562
+ details_html += f'<div style="color: #000000 !important;"><strong style="color: #000000 !important;">Inactive Days:</strong> <span style="color: #000000 !important;">{finding["days_inactive"]}</span></div>'
563
+
564
+ # Affected users with detailed information
565
+ affected_html = ''
566
+
567
+ # Check for detailed user information first (priority)
568
+ if finding.get('user_details'):
569
+ user_details = finding['user_details']
570
+ user_count = len(user_details)
571
+
572
+ # Build table for detailed user info
573
+ affected_html = f'''
574
+ <div class="optimax-affected-users">
575
+ <div class="optimax-affected-users-title" style="color: #991b1b !important;">👥 Affected Users ({user_count})</div>
576
+ <table class="optimax-user-table" style="width: 100%; margin-top: 10px; border-collapse: collapse;">
577
+ <thead>
578
+ <tr style="background: #fee2e2; border-bottom: 2px solid #991b1b;">
579
+ <th style="padding: 8px; text-align: left; color: #991b1b !important; font-weight: 700; font-size: 0.85em;">Name</th>
580
+ <th style="padding: 8px; text-align: left; color: #991b1b !important; font-weight: 700; font-size: 0.85em;">Email</th>
581
+ <th style="padding: 8px; text-align: left; color: #991b1b !important; font-weight: 700; font-size: 0.85em;">Username</th>
582
+ <th style="padding: 8px; text-align: center; color: #991b1b !important; font-weight: 700; font-size: 0.85em;">Status</th>
583
+ </tr>
584
+ </thead>
585
+ <tbody>
586
+ '''
587
+
588
+ # Show first 10 users to avoid redundancy
589
+ for i, user in enumerate(user_details[:10]):
590
+ row_bg = '#fef2f2' if i % 2 == 0 else '#ffffff'
591
+ status_color = '#10b981' if user.get('status') == 'Active' else '#dc2626'
592
+
593
+ affected_html += f'''
594
+ <tr style="background: {row_bg}; border-bottom: 1px solid #fca5a5;">
595
+ <td style="padding: 8px; color: #991b1b !important; font-size: 0.85em;">{user.get('name', 'Unknown')}</td>
596
+ <td style="padding: 8px; color: #991b1b !important; font-size: 0.85em;">{user.get('email', 'N/A')}</td>
597
+ <td style="padding: 8px; color: #991b1b !important; font-size: 0.85em; font-family: monospace;">{user.get('username', 'Unknown')}</td>
598
+ <td style="padding: 8px; text-align: center;">
599
+ <span style="display: inline-block; padding: 3px 8px; background: {row_bg}; color: {status_color} !important; font-weight: 700; font-size: 0.75em; border: 1px solid {status_color}; border-radius: 4px;">
600
+ {user.get('status', 'Unknown')}
601
+ </span>
602
+ </td>
603
+ </tr>
604
+ '''
605
+
606
+ affected_html += '</tbody></table>'
607
+
608
+ if user_count > 10:
609
+ affected_html += f'''
610
+ <div style="margin-top: 8px; color: #991b1b !important; font-size: 0.85em; font-style: italic;">
611
+ + {user_count - 10} more users not shown
612
+ </div>
613
+ '''
614
+
615
+ affected_html += '</div>'
616
+
617
+ # Fallback to simple username list if no detailed info
618
+ elif finding.get('affected_users'):
619
+ affected_users = finding['affected_users']
620
+ if len(affected_users) > 0:
621
+ user_count = len(affected_users)
622
+ # Remove duplicates while preserving order
623
+ unique_users = list(dict.fromkeys(affected_users))
624
+ users_display = ', '.join(unique_users[:5])
625
+ if len(unique_users) > 5:
626
+ users_display += f' (+{len(unique_users) - 5} more)'
627
+
628
+ affected_html = f'''
629
+ <div class="optimax-affected-users">
630
+ <div class="optimax-affected-users-title" style="color: #991b1b !important;">👥 Affected Users ({len(unique_users)})</div>
631
+ <div class="optimax-affected-users-list" style="color: #991b1b !important;">{users_display}</div>
632
+ </div>
633
+ '''
634
+
635
+ # Single user details (for individual findings)
636
+ elif finding.get('username'):
637
+ affected_html = f'''
638
+ <div class="optimax-affected-users" style="background: #fef2f2; padding: 12px 15px; border-radius: 6px; margin: 10px 0; border-left: 3px solid #dc2626;">
639
+ <table style="width: 100%; border-collapse: collapse;">
640
+ <tr>
641
+ <td style="padding: 4px 8px; color: #991b1b !important; font-weight: 700; font-size: 0.85em; width: 100px;">Name:</td>
642
+ <td style="padding: 4px 8px; color: #991b1b !important; font-size: 0.85em;">{finding.get('name', 'Unknown')}</td>
643
+ </tr>
644
+ <tr>
645
+ <td style="padding: 4px 8px; color: #991b1b !important; font-weight: 700; font-size: 0.85em;">Email:</td>
646
+ <td style="padding: 4px 8px; color: #991b1b !important; font-size: 0.85em;">{finding.get('email', 'N/A')}</td>
647
+ </tr>
648
+ <tr>
649
+ <td style="padding: 4px 8px; color: #991b1b !important; font-weight: 700; font-size: 0.85em;">Username:</td>
650
+ <td style="padding: 4px 8px; color: #991b1b !important; font-size: 0.85em; font-family: monospace;">{finding.get('username', 'Unknown')}</td>
651
+ </tr>
652
+ <tr>
653
+ <td style="padding: 4px 8px; color: #991b1b !important; font-weight: 700; font-size: 0.85em;">Status:</td>
654
+ <td style="padding: 4px 8px;">
655
+ <span style="display: inline-block; padding: 2px 8px; background: #fee2e2; color: {'#10b981' if finding.get('status') == 'Active' else '#dc2626'} !important; font-weight: 700; font-size: 0.75em; border: 1px solid {'#10b981' if finding.get('status') == 'Active' else '#dc2626'}; border-radius: 4px;">
656
+ {finding.get('status', 'Unknown')}
657
+ </span>
658
+ </td>
659
+ </tr>
660
+ </table>
661
+ </div>
662
+ '''
663
+
664
+ html += f'''
665
+ <div class="optimax-finding-card">
666
+ <div class="optimax-finding-header">
667
+ <div class="optimax-finding-title">{finding_type}</div>
668
+ <span class="optimax-severity-badge optimax-severity-{severity.lower()}">{severity}</span>
669
+ </div>
670
+ <div class="optimax-finding-body">
671
+ <div style="margin-bottom: 8px; color: #000000 !important;"><strong style="color: #000000 !important;">Description:</strong> <span style="color: #000000 !important;">{description}</span></div>
672
+ {f'<div style="margin-bottom: 8px; color: #000000 !important;"><strong style="color: #000000 !important;">Impact:</strong> <span style="color: #000000 !important;">{impact}</span></div>' if impact else ''}
673
+ {details_html}
674
+ </div>
675
+ {affected_html}
676
+ <div class="optimax-recommendation">
677
+ <div class="optimax-recommendation-title" style="color: #166534 !important;">💡 Recommendation</div>
678
+ <div class="optimax-recommendation-text" style="color: #166534 !important;">{recommendation}</div>
679
+ </div>
680
+ </div>
681
+ '''
682
+
683
+ return html
684
+
685
+
686
+ def _generate_error_html(error_msg: str) -> str:
687
+ """Generate HTML for error display"""
688
+ return f"""
689
+ <div style="padding: 30px; background: #fee2e2; border-radius: 10px; border-left: 5px solid #dc2626;">
690
+ <h2 style="color: #991b1b; margin-bottom: 10px; font-size: 1.5em;">❌ Error</h2>
691
+ <p style="color: #991b1b; font-size: 1.1em;">{error_msg}</p>
692
+ </div>
693
+ """
694
+
695
+
696
+ # Save report function
697
+ def save_report(analysis_result: dict, filename: str = None) -> str:
698
+ """Save complete HTML report to file"""
699
+ from datetime import datetime
700
+
701
+ html_content = generate_report(analysis_result)
702
+
703
+ full_html = f"""<!DOCTYPE html>
704
+ <html lang="en">
705
+ <head>
706
+ <meta charset="UTF-8">
707
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
708
+ <title>Optimax Security Report - Category-Based Analysis</title>
709
+ </head>
710
+ <body style="margin: 0; padding: 20px; background: #f9fafb !important;">
711
+ {html_content}
712
+ </body>
713
+ </html>"""
714
+
715
+ if not filename:
716
+ timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
717
+ org_name = analysis_result.get('org_name', 'Unknown').replace(' ', '_')
718
+ filename = f"optimax_report_{org_name}_{timestamp}.html"
719
+
720
+ with open(filename, 'w', encoding='utf-8') as f:
721
+ f.write(full_html)
722
+
723
+ return filename
724
+
725
+
726
+ if __name__ == '__main__':
727
+ print("✅ Category-Based Optimax Report Generator loaded!")
728
+ print(" 📁 Categories:")
729
+ print(" 1. 👤 Profiles & Permissions")
730
+ print(" 2. 🔓 Sharing Model")
731
+ print(" 3. 🔌 API & Integration")
732
+ print(" 4. 🌐 Guest/Public Exposure")
733
+ print(" 5. 💻 Custom Code")
734
+ print(" 6. 🔐 Identity & Sessions")
permission_analyzer.py ADDED
@@ -0,0 +1,352 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict, List, Optional
2
+
3
+ class PermissionAnalyzer:
4
+ """
5
+ Analyzes Salesforce permissions for security vulnerabilities
6
+ """
7
+
8
+ # Dangerous permissions that grant broad access
9
+ DANGEROUS_PERMISSIONS = {
10
+ 'PermissionsModifyAllData': {
11
+ 'severity': 'Critical',
12
+ 'description': 'Modify All Data - Full read/write access to all records',
13
+ 'impact': 'User can view, edit, and delete ALL records in the org'
14
+ },
15
+ 'PermissionsViewAllData': {
16
+ 'severity': 'Critical',
17
+ 'description': 'View All Data - Read access to all records',
18
+ 'impact': 'User can view ALL records, bypassing sharing rules'
19
+ },
20
+ 'PermissionsManageUsers': {
21
+ 'severity': 'Critical',
22
+ 'description': 'Manage Users - Can create/modify users',
23
+ 'impact': 'Can create admin users, escalate privileges'
24
+ },
25
+ 'PermissionsCustomizeApplication': {
26
+ 'severity': 'High',
27
+ 'description': 'Customize Application - Modify org configuration',
28
+ 'impact': 'Can change org settings, create fields, objects'
29
+ },
30
+ 'PermissionsAuthorApex': {
31
+ 'severity': 'High',
32
+ 'description': 'Author Apex - Write and deploy code',
33
+ 'impact': 'Can deploy malicious code, bypass security'
34
+ },
35
+ 'PermissionsExportReport': {
36
+ 'severity': 'Medium',
37
+ 'description': 'Export Reports - Export data to external files',
38
+ 'impact': 'Can export sensitive data in bulk'
39
+ },
40
+ 'PermissionsManageRoles': {
41
+ 'severity': 'High',
42
+ 'description': 'Manage Roles - Modify role hierarchy',
43
+ 'impact': 'Can manipulate data access through role changes'
44
+ },
45
+ 'PermissionsModifyMetadata': {
46
+ 'severity': 'High',
47
+ 'description': 'Modify Metadata - Change org configuration',
48
+ 'impact': 'Can alter security settings and configurations'
49
+ }
50
+ }
51
+
52
+ def analyze_all_permissions(
53
+ self,
54
+ permission_sets: List[Dict],
55
+ profiles: List[Dict],
56
+ users: List[Dict] = None # ADDED: Pass users to get details
57
+ ) -> Dict:
58
+ """
59
+ Analyze all permission sets and profiles
60
+
61
+ Returns:
62
+ Analysis results with findings
63
+ """
64
+ findings = []
65
+
66
+ # Create user lookup dictionary for faster access
67
+ user_lookup = {}
68
+ if users:
69
+ user_lookup = {u.get('Id'): u for u in users}
70
+
71
+ # Analyze permission sets
72
+ for ps in permission_sets:
73
+ ps_findings = self.analyze_permission_set(ps, user_lookup)
74
+ findings.extend(ps_findings)
75
+
76
+ # Analyze profiles
77
+ for profile in profiles:
78
+ profile_findings = self._analyze_profile_permissions(profile)
79
+ findings.extend(profile_findings)
80
+
81
+ return {
82
+ 'findings': findings,
83
+ 'permission_sets_analyzed': len(permission_sets),
84
+ 'profiles_analyzed': len(profiles),
85
+ 'dangerous_assignments': self._count_dangerous_assignments(findings)
86
+ }
87
+
88
+ def analyze_permission_set(self, permission_set: Dict, user_lookup: Dict = None) -> List[Dict]:
89
+ """
90
+ Analyze a single permission set for security issues
91
+
92
+ Args:
93
+ permission_set: Permission set data from Salesforce
94
+ user_lookup: Dictionary mapping user IDs to user records
95
+
96
+ Returns:
97
+ List of security findings
98
+ """
99
+ findings = []
100
+ ps_name = permission_set.get('Name', 'Unknown')
101
+ ps_id = permission_set.get('Id', 'Unknown')
102
+ assignments = permission_set.get('Assignments', [])
103
+
104
+ # Build detailed user list with name, email, status
105
+ user_details = []
106
+ affected_usernames = []
107
+
108
+ if user_lookup:
109
+ for assignment in assignments:
110
+ assignee = assignment.get('Assignee', {})
111
+ user_id = assignee.get('Id') or assignment.get('AssigneeId')
112
+
113
+ if user_id and user_id in user_lookup:
114
+ user = user_lookup[user_id]
115
+ user_details.append({
116
+ 'username': user.get('Username'),
117
+ 'name': user.get('Name', 'Unknown'),
118
+ 'email': user.get('Email', 'N/A'),
119
+ 'status': 'Active' if user.get('IsActive') else 'Inactive'
120
+ })
121
+ affected_usernames.append(user.get('Username', 'Unknown'))
122
+ else:
123
+ # Fallback to assignee data if available
124
+ user_details.append({
125
+ 'username': assignee.get('Username', 'Unknown'),
126
+ 'name': assignee.get('Name', 'Unknown'),
127
+ 'email': assignee.get('Email', 'N/A'),
128
+ 'status': 'Unknown'
129
+ })
130
+ affected_usernames.append(assignee.get('Username', 'Unknown'))
131
+ else:
132
+ # Fallback when user_lookup is not available
133
+ for assignment in assignments:
134
+ assignee = assignment.get('Assignee', {})
135
+ user_details.append({
136
+ 'username': assignee.get('Username', 'Unknown'),
137
+ 'name': assignee.get('Name', 'Unknown'),
138
+ 'email': assignee.get('Email', 'N/A'),
139
+ 'status': 'Unknown'
140
+ })
141
+ affected_usernames.append(assignee.get('Username', 'Unknown'))
142
+
143
+ # Check for dangerous permissions
144
+ dangerous_perms = []
145
+ for perm_field, perm_info in self.DANGEROUS_PERMISSIONS.items():
146
+ if permission_set.get(perm_field) == True:
147
+ dangerous_perms.append(perm_info)
148
+
149
+ # Create finding for each dangerous permission
150
+ findings.append({
151
+ 'type': 'Dangerous Permission in Permission Set',
152
+ 'severity': perm_info['severity'],
153
+ 'permission_set': ps_name,
154
+ 'permission_set_id': ps_id,
155
+ 'permission': perm_info['description'],
156
+ 'impact': perm_info['impact'],
157
+ 'assigned_users': len(assignments),
158
+ 'description': f"Permission Set '{ps_name}' grants {perm_info['description']} to {len(assignments)} user(s)",
159
+ 'recommendation': f"Review necessity of this permission. Consider removing or restricting to fewer users.",
160
+ 'affected_users': affected_usernames, # List of usernames
161
+ 'user_details': user_details # ADDED: Detailed user info with name, email, status
162
+ })
163
+
164
+ # Check for permission escalation risk
165
+ if len(dangerous_perms) >= 2:
166
+ findings.append({
167
+ 'type': 'Permission Escalation Risk',
168
+ 'severity': 'Critical',
169
+ 'permission_set': ps_name,
170
+ 'description': f"Permission Set '{ps_name}' combines multiple dangerous permissions",
171
+ 'impact': 'High risk of privilege escalation and data breach',
172
+ 'recommendation': 'Split into separate permission sets with single responsibilities',
173
+ 'dangerous_permissions': [p['description'] for p in dangerous_perms],
174
+ 'affected_users': affected_usernames,
175
+ 'user_details': user_details # ADDED
176
+ })
177
+
178
+ return findings
179
+
180
+ def analyze_profile(self, profile_data: Dict) -> Dict:
181
+ """
182
+ Analyze a specific profile for security issues
183
+
184
+ Args:
185
+ profile_data: Profile information from Salesforce
186
+
187
+ Returns:
188
+ Profile analysis with risk assessment
189
+ """
190
+ profile_name = profile_data.get('Name', 'Unknown')
191
+ assigned_users = profile_data.get('AssignedUsers', [])
192
+
193
+ analysis = {
194
+ 'profile_name': profile_name,
195
+ 'profile_id': profile_data.get('Id'),
196
+ 'assigned_users_count': len(assigned_users),
197
+ 'dangerous_permissions': [],
198
+ 'critical_issues': [],
199
+ 'warnings': [],
200
+ 'recommendations': [],
201
+ 'risk_score': 0
202
+ }
203
+
204
+ # Check for dangerous permissions
205
+ for perm_field, perm_info in self.DANGEROUS_PERMISSIONS.items():
206
+ if profile_data.get(perm_field) == True:
207
+ analysis['dangerous_permissions'].append(perm_info['description'])
208
+
209
+ if perm_info['severity'] == 'Critical':
210
+ analysis['critical_issues'].append({
211
+ 'permission': perm_info['description'],
212
+ 'impact': perm_info['impact'],
213
+ 'users_affected': len(assigned_users)
214
+ })
215
+ elif perm_info['severity'] == 'High':
216
+ analysis['warnings'].append({
217
+ 'permission': perm_info['description'],
218
+ 'impact': perm_info['impact']
219
+ })
220
+
221
+ # Calculate risk score
222
+ analysis['risk_score'] = self._calculate_profile_risk_score(analysis)
223
+
224
+ # Generate recommendations
225
+ analysis['recommendations'] = self._generate_profile_recommendations(analysis, profile_data)
226
+
227
+ return analysis
228
+
229
+ def _analyze_profile_permissions(self, profile: Dict) -> List[Dict]:
230
+ """
231
+ Internal method to analyze profile permissions
232
+ """
233
+ findings = []
234
+ profile_name = profile.get('Name', 'Unknown')
235
+
236
+ # Check if it's a standard profile
237
+ is_standard = not profile.get('IsCustom', False)
238
+
239
+ for perm_field, perm_info in self.DANGEROUS_PERMISSIONS.items():
240
+ if profile.get(perm_field) == True:
241
+ severity = perm_info['severity']
242
+
243
+ # Elevate severity if standard profile is modified
244
+ if is_standard and severity == 'High':
245
+ severity = 'Critical'
246
+
247
+ findings.append({
248
+ 'type': 'Dangerous Permission in Profile',
249
+ 'severity': severity,
250
+ 'profile': profile_name,
251
+ 'profile_id': profile.get('Id'),
252
+ 'permission': perm_info['description'],
253
+ 'impact': perm_info['impact'],
254
+ 'description': f"Profile '{profile_name}' has {perm_info['description']}",
255
+ 'recommendation': 'Review and restrict this permission or move to Permission Set'
256
+ })
257
+
258
+ return findings
259
+
260
+ def _calculate_profile_risk_score(self, analysis: Dict) -> int:
261
+ """
262
+ Calculate risk score for a profile (0-100)
263
+ """
264
+ score = 0
265
+
266
+ # Critical issues add 30 points each
267
+ score += len(analysis['critical_issues']) * 30
268
+
269
+ # High risk warnings add 15 points each
270
+ score += len(analysis['warnings']) * 15
271
+
272
+ # More users = higher risk
273
+ user_count = analysis['assigned_users_count']
274
+ if user_count > 50:
275
+ score += 20
276
+ elif user_count > 20:
277
+ score += 10
278
+
279
+ return min(100, score)
280
+
281
+ def _generate_profile_recommendations(self, analysis: Dict, profile_data: Dict) -> List[str]:
282
+ """
283
+ Generate specific recommendations for profile security
284
+ """
285
+ recommendations = []
286
+
287
+ if analysis['critical_issues']:
288
+ recommendations.append(
289
+ f"🚨 URGENT: Remove {len(analysis['critical_issues'])} critical permission(s) "
290
+ f"or migrate to Permission Sets for granular control"
291
+ )
292
+
293
+ if analysis['assigned_users_count'] > 20:
294
+ recommendations.append(
295
+ f"⚠️ {analysis['assigned_users_count']} users assigned to this profile. "
296
+ "Consider splitting into multiple profiles based on job functions"
297
+ )
298
+
299
+ if len(analysis['dangerous_permissions']) >= 3:
300
+ recommendations.append(
301
+ "⚠️ Profile has multiple dangerous permissions. "
302
+ "Implement least privilege principle"
303
+ )
304
+
305
+ if not recommendations:
306
+ recommendations.append("✅ Profile follows security best practices")
307
+
308
+ return recommendations
309
+
310
+ def _count_dangerous_assignments(self, findings: List[Dict]) -> int:
311
+ """
312
+ Count total dangerous permission assignments
313
+ """
314
+ count = 0
315
+ for finding in findings:
316
+ if finding.get('type') == 'Dangerous Permission in Permission Set':
317
+ count += finding.get('assigned_users', 0)
318
+ return count
319
+
320
+ def check_permission_escalation_risk(
321
+ self,
322
+ user_permissions: List[str]
323
+ ) -> Dict:
324
+ """
325
+ Check if combination of permissions creates escalation risk
326
+
327
+ Args:
328
+ user_permissions: List of permission names user has
329
+
330
+ Returns:
331
+ Risk assessment
332
+ """
333
+ risky_combinations = [
334
+ (['PermissionsAuthorApex', 'PermissionsModifyAllData'], 'Can deploy malicious code with full data access'),
335
+ (['PermissionsManageUsers', 'PermissionsViewAllData'], 'Can create admin users and access all data'),
336
+ (['PermissionsCustomizeApplication', 'PermissionsManageRoles'], 'Can manipulate security architecture')
337
+ ]
338
+
339
+ risks_found = []
340
+
341
+ for combo, impact in risky_combinations:
342
+ if all(perm in user_permissions for perm in combo):
343
+ risks_found.append({
344
+ 'combination': combo,
345
+ 'impact': impact,
346
+ 'severity': 'Critical'
347
+ })
348
+
349
+ return {
350
+ 'has_escalation_risk': len(risks_found) > 0,
351
+ 'risks': risks_found
352
+ }
prompts.py ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FULL_SCAN_PROMPT = """You are a Salesforce security expert analyzing an organization for security vulnerabilities.
2
+
3
+ Organization ID: {org_id}
4
+
5
+ Analysis Data:
6
+ {analysis_data}
7
+
8
+ Perform a comprehensive security analysis covering:
9
+
10
+ 1. **Identity & Access Management**
11
+ - Dormant admin accounts
12
+ - Excessive admin privileges
13
+ - Login anomalies
14
+
15
+ 2. **Authorization & Permissions**
16
+ - Dangerous permission sets (Modify All Data, View All Data)
17
+ - Permission escalation risks
18
+ - Least privilege violations
19
+
20
+ 3. **Record-Level Security**
21
+ - Public Read/Write OWD settings
22
+ - Overly permissive sharing rules
23
+ - Data exposure risks
24
+
25
+ For each vulnerability found:
26
+ - **Severity**: Critical, High, Medium, or Low
27
+ - **Category**: Identity, Permissions, or Sharing
28
+ - **Description**: Clear explanation of the issue
29
+ - **Impact**: Business and security implications
30
+ - **Affected Items**: Users, permission sets, or objects
31
+ - **Recommendation**: Specific remediation steps
32
+ - **Priority**: Immediate, Short-term, or Long-term
33
+
34
+ Structure your response as follows:
35
+
36
+ ## EXECUTIVE SUMMARY
37
+ [Brief overview of security posture, total vulnerabilities by severity]
38
+
39
+ ## CRITICAL FINDINGS
40
+ [List all critical vulnerabilities]
41
+
42
+ ## HIGH PRIORITY FINDINGS
43
+ [List all high priority vulnerabilities]
44
+
45
+ ## MEDIUM PRIORITY FINDINGS
46
+ [List all medium priority vulnerabilities]
47
+
48
+ ## LOW PRIORITY FINDINGS
49
+ [List all low priority vulnerabilities]
50
+
51
+ ## RECOMMENDED ACTIONS
52
+ [Prioritized list of remediation steps]
53
+
54
+ ## COMPLIANCE NOTES
55
+ [Any compliance implications - SOC 2, GDPR, etc.]
56
+
57
+ Be specific, actionable, and use clear, non-technical language where possible."""
58
+
59
+ PROFILE_SCAN_PROMPT = """You are a Salesforce security expert analyzing a specific profile for security vulnerabilities.
60
+
61
+ Profile ID: {profile_id}
62
+ Profile Name: {profile_name}
63
+
64
+ Analysis Data:
65
+ {analysis_data}
66
+
67
+ Analyze this profile for:
68
+
69
+ 1. **System Permissions**
70
+ - Administrative privileges
71
+ - Dangerous permissions (View All, Modify All)
72
+ - Setup access
73
+
74
+ 2. **Object Permissions**
75
+ - Excessive CRUD permissions
76
+ - ViewAllRecords and ModifyAllRecords
77
+ - Unnecessary object access
78
+
79
+ 3. **Field Permissions**
80
+ - Access to sensitive fields
81
+ - Edit permissions on critical data
82
+
83
+ For each vulnerability:
84
+ - **Severity**: Critical, High, Medium, or Low
85
+ - **Type**: System Permission, Object Permission, or Field Permission
86
+ - **Description**: What is the issue
87
+ - **Risk**: What could go wrong
88
+ - **Recommendation**: How to fix it
89
+
90
+ Structure your response as:
91
+
92
+ ## PROFILE OVERVIEW
93
+ [Summary of profile and its purpose]
94
+
95
+ ## SECURITY ASSESSMENT
96
+ [Overall security rating and key concerns]
97
+
98
+ ## CRITICAL ISSUES
99
+ [Critical vulnerabilities requiring immediate attention]
100
+
101
+ ## HIGH PRIORITY ISSUES
102
+ [High priority issues]
103
+
104
+ ## MEDIUM PRIORITY ISSUES
105
+ [Medium priority issues]
106
+
107
+ ## RECOMMENDATIONS
108
+ [Specific steps to improve security]
109
+
110
+ ## LEAST PRIVILEGE BASELINE
111
+ [What permissions this profile should have based on its apparent purpose]
112
+
113
+ Be specific and actionable in your recommendations."""
requirements.txt ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ============================================================================
2
+ # OPTIMAX SECURITY AGENT - REQUIREMENTS
3
+ # Version: 3.1.0 (Multi-Model AI + Anomaly Detection)
4
+ # ============================================================================
5
+
6
+ # Core Framework - Gradio 6.3.0 Compatible
7
+ gradio==6.3.0
8
+ fastapi==0.115.5
9
+ uvicorn[standard]==0.32.1
10
+ pydantic==2.10.5
11
+
12
+ # AI/ML Dependencies - Transformers & PyTorch
13
+ transformers==4.46.0
14
+ torch==2.5.1
15
+ numpy<2.0.0
16
+ sentencepiece==0.1.99
17
+ accelerate==0.34.2
18
+
19
+ # Machine Learning Libraries - Includes Isolation Forest for Anomaly Detection
20
+ scikit-learn==1.5.2 # ✅ Provides IsolationForest (no additional install needed!)
21
+ scipy==1.14.1
22
+
23
+ # Salesforce Integration
24
+ simple-salesforce==1.12.4
25
+ requests==2.31.0
26
+
27
+ # Utilities
28
+ python-dotenv==1.0.0
29
+
30
+ # ============================================================================
31
+ # NOTES:
32
+ # ============================================================================
33
+ #
34
+ # ✅ No changes needed from previous version!
35
+ #
36
+ # scikit-learn==1.5.2 already includes:
37
+ # - IsolationForest (anomaly detection)
38
+ # - cosine_similarity (used by AI models)
39
+ # - All necessary ML utilities
40
+ #
41
+ # Python Version: 3.10, 3.11, or 3.13
42
+ # Tested on: Hugging Face Spaces (Python 3.11)
43
+ #
44
+ # Total Models:
45
+ # 1. CodeBERT (125M) - from transformers
46
+ # 2. RoBERTa (125M) - from transformers
47
+ # 3. SecBERT (110M) - from transformers
48
+ # 4. Isolation Forest - from scikit-learn ✅ NEW!
49
+ #
50
+ # ============================================================================
runtime.txt ADDED
Binary file (32 Bytes). View file
 
sharing_analyzer.py ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ # analyzers/sharing_analyzer.py
3
+ from typing import Dict, List, Optional
4
+ from datetime import datetime
5
+
6
+ class SharingAnalyzer:
7
+ """
8
+ Analyzes Salesforce sharing model for security vulnerabilities
9
+ """
10
+
11
+ RISKY_OWD_SETTINGS = {
12
+ 'Public Read/Write': 'Critical',
13
+ 'Public Read/Write/Transfer': 'Critical',
14
+ 'Public Full Access': 'Critical',
15
+ 'Public Read Only': 'High'
16
+ }
17
+
18
+ def analyze_sharing_settings(self, sharing_settings: Dict) -> Dict:
19
+ """
20
+ Analyze organization-wide sharing settings
21
+
22
+ Args:
23
+ sharing_settings: OWD settings, sharing rules, role hierarchy
24
+
25
+ Returns:
26
+ Analysis results with findings
27
+ """
28
+ findings = []
29
+
30
+ # Analyze OWD settings
31
+ owd_findings = self._analyze_owd(sharing_settings.get('organization_wide_defaults', []))
32
+ findings.extend(owd_findings)
33
+
34
+ # Analyze role hierarchy
35
+ role_findings = self._analyze_role_hierarchy(sharing_settings.get('role_hierarchy', []))
36
+ findings.extend(role_findings)
37
+
38
+ # Analyze sharing rules
39
+ rule_findings = self._analyze_sharing_rules(sharing_settings.get('sharing_rules', []))
40
+ findings.extend(rule_findings)
41
+
42
+ return {
43
+ 'findings': findings,
44
+ 'owd_objects_analyzed': len(sharing_settings.get('organization_wide_defaults', [])),
45
+ 'roles_analyzed': len(sharing_settings.get('role_hierarchy', [])),
46
+ 'sharing_rules_analyzed': len(sharing_settings.get('sharing_rules', []))
47
+ }
48
+
49
+ def _analyze_owd(self, owd_settings: List[Dict]) -> List[Dict]:
50
+ """
51
+ Analyze Organization-Wide Default settings
52
+ """
53
+ findings = []
54
+
55
+ sensitive_objects = ['Account', 'Contact', 'Opportunity', 'Contract', 'Case']
56
+
57
+ for setting in owd_settings:
58
+ obj_name = setting.get('ObjectName', 'Unknown')
59
+ default_access = setting.get('DefaultAccess', 'Private')
60
+
61
+ # Check if sensitive object has public access
62
+ if obj_name in sensitive_objects and default_access in self.RISKY_OWD_SETTINGS:
63
+ severity = self.RISKY_OWD_SETTINGS[default_access]
64
+
65
+ findings.append({
66
+ 'type': 'Insecure Organization-Wide Default',
67
+ 'severity': severity,
68
+ 'object': obj_name,
69
+ 'current_setting': default_access,
70
+ 'description': f"{obj_name} has {default_access} OWD setting",
71
+ 'impact': f"All users can access {obj_name} records by default",
72
+ 'recommendation': f"Change {obj_name} OWD to Private and use sharing rules for controlled access"
73
+ })
74
+
75
+ return findings
76
+
77
+ def _analyze_role_hierarchy(self, roles: List[Dict]) -> List[Dict]:
78
+ """
79
+ Analyze role hierarchy for security issues
80
+ """
81
+ findings = []
82
+
83
+ # Check for flat hierarchy (security issue)
84
+ roles_without_parent = [r for r in roles if not r.get('ParentRoleId')]
85
+
86
+ if len(roles_without_parent) > len(roles) * 0.5:
87
+ findings.append({
88
+ 'type': 'Flat Role Hierarchy',
89
+ 'severity': 'Medium',
90
+ 'description': 'Role hierarchy is too flat',
91
+ 'impact': 'Users may have unintended access through role hierarchy',
92
+ 'recommendation': 'Design role hierarchy based on data access needs, not org chart',
93
+ 'roles_without_parent': len(roles_without_parent)
94
+ })
95
+
96
+ return findings
97
+
98
+ def _analyze_sharing_rules(self, sharing_rules: List[Dict]) -> List[Dict]:
99
+ """
100
+ Analyze sharing rules for over-broad access
101
+ """
102
+ findings = []
103
+
104
+ for rule in sharing_rules:
105
+ access_level = rule.get('AccessLevel', 'Read')
106
+
107
+ if access_level in ['Edit', 'All']:
108
+ findings.append({
109
+ 'type': 'Broad Sharing Rule',
110
+ 'severity': 'Medium',
111
+ 'rule_name': rule.get('DeveloperName', 'Unknown'),
112
+ 'object': rule.get('SobjectType', 'Unknown'),
113
+ 'access_level': access_level,
114
+ 'description': f"Sharing rule grants {access_level} access",
115
+ 'recommendation': 'Review necessity of Edit/All access in sharing rules'
116
+ })
117
+
118
+ return findings
119
+
120
+ def check_sharing_bypass_permissions(self, user_permissions: List[str]) -> bool:
121
+ """
122
+ Check if user has permissions that bypass sharing rules
123
+ """
124
+ bypass_permissions = [
125
+ 'PermissionsViewAllData',
126
+ 'PermissionsModifyAllData'
127
+ ]
128
+
129
+ return any(perm in user_permissions for perm in bypass_permissions)
vulenerabilities_db.py ADDED
@@ -0,0 +1,272 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Vulnerability Database
3
+ Knowledge base of known Salesforce security vulnerabilities and patterns
4
+ """
5
+
6
+ class VulnerabilityDatabase:
7
+ """Database of known Salesforce security vulnerabilities"""
8
+
9
+ # Dangerous system permissions
10
+ DANGEROUS_PERMISSIONS = {
11
+ 'ModifyAllData': {
12
+ 'severity': 'critical',
13
+ 'category': 'Data Access',
14
+ 'description': 'User can create, edit, and delete all records regardless of sharing settings',
15
+ 'risk': 'Complete data manipulation capability - can alter financial records, delete data, bypass all security',
16
+ 'recommendation': 'Remove immediately. Grant object-specific permissions instead.',
17
+ 'compliance_impact': 'SOC 2, GDPR, HIPAA violation risk',
18
+ 'cve_reference': None,
19
+ 'owasp_category': 'A01:2021 - Broken Access Control'
20
+ },
21
+ 'ViewAllData': {
22
+ 'severity': 'critical',
23
+ 'category': 'Data Access',
24
+ 'description': 'User can view all records regardless of sharing settings',
25
+ 'risk': 'Complete visibility to sensitive data including PII, financial data, trade secrets',
26
+ 'recommendation': 'Remove and grant object-specific read permissions only where needed.',
27
+ 'compliance_impact': 'GDPR, HIPAA, PCI-DSS violation risk',
28
+ 'cve_reference': None,
29
+ 'owasp_category': 'A01:2021 - Broken Access Control'
30
+ },
31
+ 'ManageUsers': {
32
+ 'severity': 'high',
33
+ 'category': 'User Management',
34
+ 'description': 'User can create, edit, and deactivate users',
35
+ 'risk': 'Can create admin accounts, lock out legitimate users, disable security controls',
36
+ 'recommendation': 'Restrict to designated HR and IT administrators only.',
37
+ 'compliance_impact': 'SOC 2 - Access control violation',
38
+ 'cve_reference': None,
39
+ 'owasp_category': 'A01:2021 - Broken Access Control'
40
+ },
41
+ 'CustomizeApplication': {
42
+ 'severity': 'high',
43
+ 'category': 'Configuration',
44
+ 'description': 'User can modify org configuration including security settings',
45
+ 'risk': 'Can weaken security controls, modify validation rules, change workflows',
46
+ 'recommendation': 'Restrict to administrators and approved developers only.',
47
+ 'compliance_impact': 'SOC 2 - Change management violation',
48
+ 'cve_reference': None,
49
+ 'owasp_category': 'A05:2021 - Security Misconfiguration'
50
+ },
51
+ 'AuthorApex': {
52
+ 'severity': 'high',
53
+ 'category': 'Development',
54
+ 'description': 'User can write and deploy Apex code',
55
+ 'risk': 'Can introduce backdoors, data exfiltration code, malicious logic',
56
+ 'recommendation': 'Restrict to vetted developers only. Implement code review process.',
57
+ 'compliance_impact': 'SOC 2 - Development security violation',
58
+ 'cve_reference': None,
59
+ 'owasp_category': 'A04:2021 - Insecure Design'
60
+ },
61
+ 'ViewSetup': {
62
+ 'severity': 'medium',
63
+ 'category': 'Information Disclosure',
64
+ 'description': 'User can view setup and configuration information',
65
+ 'risk': 'Can map out security architecture for targeted attacks',
66
+ 'recommendation': 'Limit to administrators and necessary support staff.',
67
+ 'compliance_impact': 'Information disclosure',
68
+ 'cve_reference': None,
69
+ 'owasp_category': 'A01:2021 - Broken Access Control'
70
+ },
71
+ 'ManageInternalUsers': {
72
+ 'severity': 'high',
73
+ 'category': 'User Management',
74
+ 'description': 'Can manage internal users including password resets',
75
+ 'risk': 'Account takeover, unauthorized access escalation',
76
+ 'recommendation': 'Restrict to IT security team only.',
77
+ 'compliance_impact': 'SOC 2 - Access control',
78
+ 'cve_reference': None,
79
+ 'owasp_category': 'A01:2021 - Broken Access Control'
80
+ }
81
+ }
82
+
83
+ # Sharing model vulnerabilities
84
+ SHARING_VULNERABILITIES = {
85
+ 'PublicReadWrite': {
86
+ 'severity': 'critical',
87
+ 'objects': ['Account', 'Contact', 'Opportunity', 'Lead', 'Case', 'Quote', 'Contract'],
88
+ 'description': 'All users can read and write all records',
89
+ 'risk': 'Data integrity compromise, unauthorized modifications, compliance violations',
90
+ 'recommendation': 'Change to Private. Use sharing rules for controlled access.',
91
+ 'compliance_impact': 'GDPR, SOC 2, PCI-DSS violations'
92
+ },
93
+ 'PublicRead': {
94
+ 'severity': 'high',
95
+ 'objects': ['Account', 'Contact', 'Lead', 'Case', 'Opportunity'],
96
+ 'description': 'All users can view all records',
97
+ 'risk': 'Sensitive data exposure, privacy violations',
98
+ 'recommendation': 'Change to Private. Implement role hierarchy or sharing rules.',
99
+ 'compliance_impact': 'GDPR, HIPAA privacy violations'
100
+ },
101
+ 'ControlledByParent': {
102
+ 'severity': 'medium',
103
+ 'objects': ['Contact', 'Opportunity', 'Case'],
104
+ 'description': 'Inherits sharing from parent record',
105
+ 'risk': 'Unintended access if parent sharing is too permissive',
106
+ 'recommendation': 'Verify parent object sharing is appropriate.',
107
+ 'compliance_impact': 'Potential data leakage'
108
+ }
109
+ }
110
+
111
+ # Identity and access vulnerabilities
112
+ IDENTITY_VULNERABILITIES = {
113
+ 'DormantAdminAccount': {
114
+ 'severity': 'high',
115
+ 'threshold_days': 90,
116
+ 'description': 'Administrator account inactive for 90+ days',
117
+ 'risk': 'Orphaned privileged accounts are prime targets for attackers',
118
+ 'recommendation': 'Deactivate or remove admin privileges immediately.',
119
+ 'compliance_impact': 'SOC 2, PCI-DSS - Access review violation'
120
+ },
121
+ 'NoMFA': {
122
+ 'severity': 'high',
123
+ 'description': 'User does not have MFA enabled',
124
+ 'risk': 'Account vulnerable to credential theft and phishing',
125
+ 'recommendation': 'Enable MFA for all users, especially administrators.',
126
+ 'compliance_impact': 'SOC 2, PCI-DSS - Authentication requirement'
127
+ },
128
+ 'FailedLoginAttempts': {
129
+ 'severity': 'medium',
130
+ 'threshold': 5,
131
+ 'description': 'Multiple failed login attempts detected',
132
+ 'risk': 'Possible brute force attack or credential stuffing',
133
+ 'recommendation': 'Investigate source IPs, consider account lockout policies.',
134
+ 'compliance_impact': 'Security monitoring requirement'
135
+ },
136
+ 'UnusualLoginTime': {
137
+ 'severity': 'medium',
138
+ 'description': 'Login outside normal business hours',
139
+ 'risk': 'Compromised credentials or unauthorized access',
140
+ 'recommendation': 'Implement time-based login restrictions, investigate anomalies.',
141
+ 'compliance_impact': 'Anomaly detection'
142
+ }
143
+ }
144
+
145
+ # Known attack patterns
146
+ ATTACK_PATTERNS = {
147
+ 'PrivilegeEscalation': {
148
+ 'indicators': [
149
+ 'Multiple permission sets assigned to non-admin user',
150
+ 'Permission set with admin-level permissions',
151
+ 'Profile modification to add dangerous permissions'
152
+ ],
153
+ 'severity': 'critical',
154
+ 'description': 'User gaining elevated privileges through permission accumulation',
155
+ 'mitigation': 'Regular permission audits, least privilege enforcement'
156
+ },
157
+ 'DataExfiltration': {
158
+ 'indicators': [
159
+ 'Excessive report exports',
160
+ 'API usage spikes',
161
+ 'Large data exports',
162
+ 'Access to View All Data'
163
+ ],
164
+ 'severity': 'critical',
165
+ 'description': 'Suspicious data access patterns indicating potential theft',
166
+ 'mitigation': 'Monitor API usage, implement data loss prevention'
167
+ },
168
+ 'AccountTakeover': {
169
+ 'indicators': [
170
+ 'Login from new geographic location',
171
+ 'Multiple failed logins followed by success',
172
+ 'Password reset without MFA',
173
+ 'Unusual activity after login'
174
+ ],
175
+ 'severity': 'critical',
176
+ 'description': 'Compromised user account being accessed by attacker',
177
+ 'mitigation': 'Enforce MFA, monitor login anomalies, implement IP restrictions'
178
+ }
179
+ }
180
+
181
+ # Compliance requirements
182
+ COMPLIANCE_MAPPINGS = {
183
+ 'SOC2': {
184
+ 'CC6.1': 'Logical and physical access controls',
185
+ 'CC6.2': 'Prior to issuing system credentials',
186
+ 'CC6.3': 'Removes access when terminated',
187
+ 'CC6.6': 'Manages system-to-system communications',
188
+ 'CC7.2': 'Monitors system components'
189
+ },
190
+ 'GDPR': {
191
+ 'Article_5': 'Principles relating to processing',
192
+ 'Article_25': 'Data protection by design and default',
193
+ 'Article_32': 'Security of processing'
194
+ },
195
+ 'HIPAA': {
196
+ '164.308': 'Administrative safeguards',
197
+ '164.312': 'Technical safeguards'
198
+ },
199
+ 'PCI_DSS': {
200
+ 'Req_7': 'Restrict access to cardholder data',
201
+ 'Req_8': 'Identify and authenticate access',
202
+ 'Req_10': 'Track and monitor all access'
203
+ }
204
+ }
205
+
206
+ @classmethod
207
+ def get_permission_info(cls, permission_name: str) -> dict:
208
+ """Get information about a specific permission"""
209
+ return cls.DANGEROUS_PERMISSIONS.get(permission_name, {
210
+ 'severity': 'low',
211
+ 'description': f'Permission: {permission_name}',
212
+ 'recommendation': 'Review if this permission is necessary.'
213
+ })
214
+
215
+ @classmethod
216
+ def get_sharing_vulnerability(cls, sharing_model: str) -> dict:
217
+ """Get vulnerability info for sharing model"""
218
+ for vuln_type, info in cls.SHARING_VULNERABILITIES.items():
219
+ if vuln_type in sharing_model:
220
+ return info
221
+ return {}
222
+
223
+ @classmethod
224
+ def get_identity_vulnerability(cls, vuln_type: str) -> dict:
225
+ """Get identity vulnerability information"""
226
+ return cls.IDENTITY_VULNERABILITIES.get(vuln_type, {})
227
+
228
+ @classmethod
229
+ def check_compliance_impact(cls, finding: dict) -> list:
230
+ """Check which compliance frameworks are impacted"""
231
+ impacts = []
232
+
233
+ if finding.get('severity') in ['critical', 'high']:
234
+ impacts.extend(['SOC2', 'GDPR'])
235
+
236
+ if 'data' in finding.get('category', '').lower():
237
+ impacts.extend(['GDPR', 'HIPAA'])
238
+
239
+ if 'access' in finding.get('type', '').lower():
240
+ impacts.extend(['SOC2', 'PCI_DSS'])
241
+
242
+ return list(set(impacts))
243
+
244
+ @classmethod
245
+ def get_remediation_priority(cls, severity: str, compliance_impact: list) -> int:
246
+ """Calculate remediation priority (1=highest, 5=lowest)"""
247
+ base_priority = {
248
+ 'critical': 1,
249
+ 'high': 2,
250
+ 'medium': 3,
251
+ 'low': 4,
252
+ 'info': 5
253
+ }.get(severity, 5)
254
+
255
+ # Increase priority if compliance is impacted
256
+ if compliance_impact and base_priority > 1:
257
+ base_priority -= 1
258
+
259
+ return base_priority
260
+
261
+ @classmethod
262
+ def get_similar_vulnerabilities(cls, vulnerability_type: str) -> list:
263
+ """Get similar vulnerabilities to watch for"""
264
+
265
+ similarity_map = {
266
+ 'ModifyAllData': ['ViewAllData', 'ManageUsers'],
267
+ 'ViewAllData': ['ModifyAllData', 'ViewSetup'],
268
+ 'PublicReadWrite': ['PublicRead', 'ControlledByParent'],
269
+ 'DormantAdminAccount': ['NoMFA', 'UnusualLoginTime']
270
+ }
271
+
272
+ return similarity_map.get(vulnerability_type, [])