skylos 1.0.10__py3-none-any.whl → 2.5.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. skylos/__init__.py +9 -3
  2. skylos/analyzer.py +674 -168
  3. skylos/cfg_visitor.py +60 -0
  4. skylos/cli.py +719 -235
  5. skylos/codemods.py +277 -0
  6. skylos/config.py +50 -0
  7. skylos/constants.py +78 -0
  8. skylos/gatekeeper.py +147 -0
  9. skylos/linter.py +18 -0
  10. skylos/rules/base.py +20 -0
  11. skylos/rules/danger/calls.py +119 -0
  12. skylos/rules/danger/danger.py +157 -0
  13. skylos/rules/danger/danger_cmd/cmd_flow.py +75 -0
  14. skylos/rules/danger/danger_fs/__init__.py +0 -0
  15. skylos/rules/danger/danger_fs/path_flow.py +79 -0
  16. skylos/rules/danger/danger_net/__init__.py +0 -0
  17. skylos/rules/danger/danger_net/ssrf_flow.py +80 -0
  18. skylos/rules/danger/danger_sql/__init__.py +0 -0
  19. skylos/rules/danger/danger_sql/sql_flow.py +245 -0
  20. skylos/rules/danger/danger_sql/sql_raw_flow.py +96 -0
  21. skylos/rules/danger/danger_web/__init__.py +0 -0
  22. skylos/rules/danger/danger_web/xss_flow.py +170 -0
  23. skylos/rules/danger/taint.py +110 -0
  24. skylos/rules/quality/__init__.py +0 -0
  25. skylos/rules/quality/complexity.py +95 -0
  26. skylos/rules/quality/logic.py +96 -0
  27. skylos/rules/quality/nesting.py +101 -0
  28. skylos/rules/quality/structure.py +99 -0
  29. skylos/rules/secrets.py +325 -0
  30. skylos/server.py +554 -0
  31. skylos/visitor.py +502 -90
  32. skylos/visitors/__init__.py +0 -0
  33. skylos/visitors/framework_aware.py +437 -0
  34. skylos/visitors/test_aware.py +74 -0
  35. skylos-2.5.2.dist-info/METADATA +21 -0
  36. skylos-2.5.2.dist-info/RECORD +42 -0
  37. {skylos-1.0.10.dist-info → skylos-2.5.2.dist-info}/WHEEL +1 -1
  38. {skylos-1.0.10.dist-info → skylos-2.5.2.dist-info}/top_level.txt +0 -1
  39. skylos-1.0.10.dist-info/METADATA +0 -8
  40. skylos-1.0.10.dist-info/RECORD +0 -21
  41. test/compare_tools.py +0 -604
  42. test/diagnostics.py +0 -364
  43. test/sample_repo/app.py +0 -13
  44. test/sample_repo/sample_repo/commands.py +0 -81
  45. test/sample_repo/sample_repo/models.py +0 -122
  46. test/sample_repo/sample_repo/routes.py +0 -89
  47. test/sample_repo/sample_repo/utils.py +0 -36
  48. test/test_skylos.py +0 -456
  49. test/test_visitor.py +0 -220
  50. {test → skylos/rules}/__init__.py +0 -0
  51. {test/sample_repo → skylos/rules/danger}/__init__.py +0 -0
  52. {test/sample_repo/sample_repo → skylos/rules/danger/danger_cmd}/__init__.py +0 -0
  53. {skylos-1.0.10.dist-info → skylos-2.5.2.dist-info}/entry_points.txt +0 -0
skylos/server.py ADDED
@@ -0,0 +1,554 @@
1
+ from flask import Flask, request, jsonify
2
+ from flask_cors import CORS
3
+ import skylos
4
+ import json
5
+ import os
6
+ import webbrowser
7
+ from threading import Timer
8
+
9
+ app = Flask(__name__)
10
+ CORS(app)
11
+
12
+
13
+ @app.route("/")
14
+ def serve_frontend():
15
+ return """<!DOCTYPE html>
16
+ <html lang="en">
17
+ <head>
18
+ <meta charset="UTF-8">
19
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
20
+ <title>Skylos Dead Code Analyzer</title>
21
+ <style>
22
+ * {
23
+ margin: 0;
24
+ padding: 0;
25
+ box-sizing: border-box;
26
+ }
27
+
28
+ body {
29
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica Neue', sans-serif;
30
+ background: #000000;
31
+ color: #ffffff;
32
+ min-height: 100vh;
33
+ padding: 20px;
34
+ }
35
+
36
+ .container {
37
+ max-width: 1400px;
38
+ margin: 0 auto;
39
+ }
40
+
41
+ .header {
42
+ text-align: center;
43
+ margin-bottom: 40px;
44
+ }
45
+
46
+ .header h1 {
47
+ font-size: 3rem;
48
+ margin-bottom: 10px;
49
+ color: #ffffff;
50
+ font-weight: 300;
51
+ letter-spacing: -1px;
52
+ }
53
+
54
+ .header p {
55
+ color: #888888;
56
+ font-size: 1.1rem;
57
+ margin-top: 10px;
58
+ font-weight: 400;
59
+ }
60
+
61
+ .controls {
62
+ background: #111111;
63
+ border: 1px solid #333333;
64
+ border-radius: 12px;
65
+ padding: 32px;
66
+ margin-bottom: 32px;
67
+ }
68
+
69
+ .folder-input {
70
+ margin-bottom: 25px;
71
+ }
72
+
73
+ .folder-input label {
74
+ display: block;
75
+ font-weight: bold;
76
+ margin-bottom: 10px;
77
+ color: #ffffff;
78
+ }
79
+
80
+ .folder-input input {
81
+ width: 100%;
82
+ padding: 16px;
83
+ background: #222222;
84
+ color: #ffffff;
85
+ border: 1px solid #444444;
86
+ border-radius: 8px;
87
+ font-family: inherit;
88
+ font-size: 14px;
89
+ transition: border-color 0.2s ease;
90
+ }
91
+
92
+ .folder-input input:focus {
93
+ outline: none;
94
+ border-color: #ffffff;
95
+ }
96
+
97
+ .folder-input input::placeholder {
98
+ color: #888888;
99
+ }
100
+
101
+ .analyze-btn {
102
+ background: #ffffff;
103
+ color: #000000;
104
+ border: none;
105
+ padding: 16px 32px;
106
+ border-radius: 8px;
107
+ cursor: pointer;
108
+ font-weight: 500;
109
+ font-family: inherit;
110
+ font-size: 14px;
111
+ margin-right: 15px;
112
+ transition: all 0.2s ease;
113
+ }
114
+
115
+ .analyze-btn:hover {
116
+ background: #f0f0f0;
117
+ transform: translateY(-1px);
118
+ }
119
+
120
+ .analyze-btn:disabled {
121
+ background: #666666;
122
+ color: #ffffff;
123
+ cursor: not-allowed;
124
+ transform: none;
125
+ }
126
+
127
+ .confidence-control {
128
+ margin-top: 25px;
129
+ }
130
+
131
+ .confidence-control label {
132
+ display: block;
133
+ font-weight: bold;
134
+ margin-bottom: 10px;
135
+ color: #ffffff;
136
+ }
137
+
138
+ .confidence-slider {
139
+ width: 100%;
140
+ height: 6px;
141
+ background: #333333;
142
+ outline: none;
143
+ border-radius: 3px;
144
+ -webkit-appearance: none;
145
+ }
146
+
147
+ .confidence-slider::-webkit-slider-thumb {
148
+ -webkit-appearance: none;
149
+ appearance: none;
150
+ width: 20px;
151
+ height: 20px;
152
+ border-radius: 50%;
153
+ background: #ffffff;
154
+ cursor: pointer;
155
+ }
156
+
157
+ .confidence-slider::-moz-range-thumb {
158
+ width: 20px;
159
+ height: 20px;
160
+ border-radius: 50%;
161
+ background: #ffffff;
162
+ cursor: pointer;
163
+ border: none;
164
+ }
165
+
166
+ .summary {
167
+ background: #111111;
168
+ border: 1px solid #333333;
169
+ border-radius: 8px;
170
+ padding: 25px;
171
+ margin-bottom: 30px;
172
+ }
173
+
174
+ .summary h2 {
175
+ margin-bottom: 20px;
176
+ color: #ffffff;
177
+ }
178
+
179
+ .summary-grid {
180
+ display: grid;
181
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
182
+ gap: 15px;
183
+ }
184
+
185
+ .summary-item {
186
+ background: #222222;
187
+ border: 1px solid #444444;
188
+ border-radius: 4px;
189
+ padding: 15px;
190
+ text-align: center;
191
+ }
192
+
193
+ .summary-item .count {
194
+ font-size: 2rem;
195
+ font-weight: bold;
196
+ color: #ffffff;
197
+ margin-bottom: 5px;
198
+ }
199
+
200
+ .summary-item .label {
201
+ color: #cccccc;
202
+ text-transform: uppercase;
203
+ font-size: 0.9rem;
204
+ }
205
+
206
+ .results {
207
+ background: #111111;
208
+ border: 1px solid #333333;
209
+ border-radius: 8px;
210
+ padding: 25px;
211
+ }
212
+
213
+ .results h2 {
214
+ margin-bottom: 20px;
215
+ color: #ffffff;
216
+ }
217
+
218
+ .dead-code-list {
219
+ max-height: 600px;
220
+ overflow-y: auto;
221
+ }
222
+
223
+ .dead-code-item {
224
+ background: #222222;
225
+ border: 1px solid #444444;
226
+ border-radius: 4px;
227
+ padding: 15px;
228
+ margin-bottom: 10px;
229
+ display: flex;
230
+ justify-content: space-between;
231
+ align-items: center;
232
+ }
233
+
234
+ .item-details {
235
+ flex-grow: 1;
236
+ }
237
+
238
+ .item-name {
239
+ font-weight: bold;
240
+ color: #ffffff;
241
+ margin-bottom: 5px;
242
+ }
243
+
244
+ .item-location {
245
+ color: #999999;
246
+ font-size: 0.9rem;
247
+ }
248
+
249
+ .item-meta {
250
+ display: flex;
251
+ align-items: center;
252
+ gap: 10px;
253
+ }
254
+
255
+ .item-type {
256
+ background: #ffffff;
257
+ color: #000000;
258
+ padding: 6px 12px;
259
+ border-radius: 6px;
260
+ font-size: 0.8rem;
261
+ font-weight: 500;
262
+ text-transform: uppercase;
263
+ letter-spacing: 0.5px;
264
+ }
265
+
266
+ .item-confidence {
267
+ color: #ffffff;
268
+ font-weight: bold;
269
+ }
270
+
271
+ .no-results {
272
+ text-align: center;
273
+ padding: 40px;
274
+ color: #666666;
275
+ }
276
+
277
+ .loading {
278
+ text-align: center;
279
+ padding: 40px;
280
+ color: #ffffff;
281
+ }
282
+
283
+ .error {
284
+ background: #330000;
285
+ border: 1px solid #660000;
286
+ color: #ff6666;
287
+ padding: 15px;
288
+ border-radius: 4px;
289
+ margin-bottom: 20px;
290
+ }
291
+
292
+ ::-webkit-scrollbar {
293
+ width: 8px;
294
+ }
295
+
296
+ ::-webkit-scrollbar-track {
297
+ background: #222222;
298
+ }
299
+
300
+ ::-webkit-scrollbar-thumb {
301
+ background: #444444;
302
+ border-radius: 4px;
303
+ }
304
+
305
+ ::-webkit-scrollbar-thumb:hover {
306
+ background: #666666;
307
+ }
308
+ </style>
309
+ </head>
310
+ <body>
311
+ <div class="container">
312
+ <div class="header">
313
+ <h1>Skylos Dead Code Analyzer</h1>
314
+ <p>Find and eliminate unused code in your Python projects</p>
315
+ </div>
316
+
317
+ <div class="controls">
318
+ <div class="folder-input">
319
+ <label for="folderPath">Project Path:</label>
320
+ <input type="text" id="folderPath" placeholder="/path/to/your/python/project" value="./">
321
+ </div>
322
+
323
+ <button class="analyze-btn" id="analyzeBtn">Analyze Project</button>
324
+
325
+ <div class="confidence-control">
326
+ <label for="confidenceSlider">Confidence Threshold: <span id="confidenceValue">60</span>%</label>
327
+ <input type="range" id="confidenceSlider" class="confidence-slider"
328
+ min="0" max="100" value="60" step="1">
329
+ </div>
330
+ </div>
331
+
332
+ <div id="errorMessage"></div>
333
+
334
+ <div class="summary">
335
+ <h2>Summary</h2>
336
+ <div class="summary-grid">
337
+ <div class="summary-item">
338
+ <div class="count" id="functionsCount">0</div>
339
+ <div class="label">Unreachable Functions</div>
340
+ </div>
341
+ <div class="summary-item">
342
+ <div class="count" id="importsCount">0</div>
343
+ <div class="label">Unused Imports</div>
344
+ </div>
345
+ <div class="summary-item">
346
+ <div class="count" id="parametersCount">0</div>
347
+ <div class="label">Unused Parameters</div>
348
+ </div>
349
+ <div class="summary-item">
350
+ <div class="count" id="variablesCount">0</div>
351
+ <div class="label">Unused Variables</div>
352
+ </div>
353
+ <div class="summary-item">
354
+ <div class="count" id="classesCount">0</div>
355
+ <div class="label">Unused Classes</div>
356
+ </div>
357
+ </div>
358
+ </div>
359
+
360
+ <div class="results">
361
+ <h2>Dead Code Items</h2>
362
+ <div class="dead-code-list" id="deadCodeList">
363
+ <div class="no-results">
364
+ <p>Enter a project path and click "Analyze Project" to scan for dead code.</p>
365
+ </div>
366
+ </div>
367
+ </div>
368
+ </div>
369
+
370
+ <script>
371
+ let analysisData = null;
372
+ let confidenceThreshold = 60;
373
+
374
+ const slider = document.getElementById('confidenceSlider');
375
+ const confidenceValue = document.getElementById('confidenceValue');
376
+ const analyzeBtn = document.getElementById('analyzeBtn');
377
+ const folderPath = document.getElementById('folderPath');
378
+ const errorMessage = document.getElementById('errorMessage');
379
+
380
+ slider.addEventListener('input', (e) => {
381
+ confidenceThreshold = parseInt(e.target.value);
382
+ confidenceValue.textContent = confidenceThreshold;
383
+ if (analysisData) {
384
+ updateDisplay();
385
+ }
386
+ });
387
+
388
+ analyzeBtn.addEventListener('click', analyzeProject);
389
+
390
+ function showError(message) {
391
+ errorMessage.innerHTML = `<div class="error">${message}</div>`;
392
+ }
393
+
394
+ function clearError() {
395
+ errorMessage.innerHTML = '';
396
+ }
397
+
398
+ async function analyzeProject() {
399
+ const path = folderPath.value.trim();
400
+ if (!path) {
401
+ showError('Please enter a project path');
402
+ return;
403
+ }
404
+
405
+ clearError();
406
+ analyzeBtn.textContent = 'Analyzing...';
407
+ analyzeBtn.disabled = true;
408
+
409
+ document.getElementById('deadCodeList').innerHTML = '<div class="loading">Analyzing project...</div>';
410
+
411
+ try {
412
+ const response = await fetch('/api/analyze', {
413
+ method: 'POST',
414
+ headers: {
415
+ 'Content-Type': 'application/json',
416
+ },
417
+ body: JSON.stringify({
418
+ path: path,
419
+ confidence: confidenceThreshold
420
+ })
421
+ });
422
+
423
+ if (!response.ok) {
424
+ const errorData = await response.json();
425
+ throw new Error(errorData.error || `Analysis failed: ${response.statusText}`);
426
+ }
427
+
428
+ const result = await response.json();
429
+ analysisData = result;
430
+ updateDisplay();
431
+
432
+ } catch (error) {
433
+ showError(`Error: ${error.message}`);
434
+ document.getElementById('deadCodeList').innerHTML = '<div class="no-results"><p>Analysis failed. Check the error message above.</p></div>';
435
+ }
436
+
437
+ analyzeBtn.textContent = 'Analyze Project';
438
+ analyzeBtn.disabled = false;
439
+ }
440
+
441
+ function updateDisplay() {
442
+ if (!analysisData) return;
443
+
444
+ const filteredData = getFilteredData();
445
+ updateSummary(filteredData);
446
+ updateDeadCodeList(filteredData);
447
+ }
448
+
449
+ function getFilteredData() {
450
+ const data = {
451
+ functions: analysisData.unused_functions || [],
452
+ imports: analysisData.unused_imports || [],
453
+ parameters: analysisData.unused_parameters || [],
454
+ variables: analysisData.unused_variables || [],
455
+ classes: analysisData.unused_classes || []
456
+ };
457
+
458
+ Object.keys(data).forEach(key => {
459
+ data[key] = data[key].filter(item => item.confidence >= confidenceThreshold);
460
+ });
461
+
462
+ return data;
463
+ }
464
+
465
+ function updateSummary(data) {
466
+ document.getElementById('functionsCount').textContent = data.functions.length;
467
+ document.getElementById('importsCount').textContent = data.imports.length;
468
+ document.getElementById('parametersCount').textContent = data.parameters.length;
469
+ document.getElementById('variablesCount').textContent = data.variables.length;
470
+ document.getElementById('classesCount').textContent = data.classes.length;
471
+ }
472
+
473
+ function updateDeadCodeList(data) {
474
+ const listElement = document.getElementById('deadCodeList');
475
+ const allItems = [];
476
+
477
+ Object.keys(data).forEach(category => {
478
+ data[category].forEach(item => {
479
+ allItems.push({
480
+ ...item,
481
+ category: category.slice(0, -1)
482
+ });
483
+ });
484
+ });
485
+
486
+ if (allItems.length === 0) {
487
+ listElement.innerHTML = `
488
+ <div class="no-results">
489
+ <p>No dead code found at confidence level ${confidenceThreshold}%</p>
490
+ </div>
491
+ `;
492
+ return;
493
+ }
494
+
495
+ allItems.sort((a, b) => b.confidence - a.confidence);
496
+
497
+ listElement.innerHTML = allItems.map(item => `
498
+ <div class="dead-code-item">
499
+ <div class="item-details">
500
+ <div class="item-name">${item.name}</div>
501
+ <div class="item-location">${item.file}:${item.line}</div>
502
+ </div>
503
+ <div class="item-meta">
504
+ <span class="item-type">${item.category}</span>
505
+ <span class="item-confidence">${item.confidence}%</span>
506
+ </div>
507
+ </div>
508
+ `).join('');
509
+ }
510
+ </script>
511
+ </body>
512
+ </html>"""
513
+
514
+
515
+ @app.route("/api/analyze", methods=["POST"])
516
+ def analyze_project():
517
+ """Analyze a project using skylos"""
518
+ try:
519
+ data = request.json
520
+ path = data.get("path")
521
+ confidence = data.get("confidence", 60)
522
+
523
+ if not path:
524
+ return jsonify({"error": "Path is required"}), 400
525
+
526
+ if not os.path.exists(path):
527
+ return jsonify({"error": f"Path does not exist: {path}"}), 400
528
+
529
+ # Call your actual skylos analyzer
530
+ result_json = skylos.analyze(path, conf=confidence)
531
+ result = json.loads(result_json)
532
+
533
+ return jsonify(result)
534
+
535
+ except Exception as e:
536
+ return jsonify({"error": str(e)}), 500
537
+
538
+
539
+ def start_server():
540
+ """Start the web server"""
541
+
542
+ def open_browser():
543
+ webbrowser.open("http://localhost:5090")
544
+
545
+ print(" Starting Skylos Web Interface...")
546
+ print("Opening browser at: http://localhost:5090")
547
+
548
+ Timer(1.5, open_browser).start()
549
+
550
+ app.run(debug=False, host="0.0.0.0", port=5090, use_reloader=False)
551
+
552
+
553
+ if __name__ == "__main__":
554
+ start_server()