skylos 2.2.3__tar.gz → 2.4.0__tar.gz
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.
Potentially problematic release.
This version of skylos might be problematic. Click here for more details.
- {skylos-2.2.3 → skylos-2.4.0}/PKG-INFO +1 -1
- {skylos-2.2.3 → skylos-2.4.0}/README.md +150 -81
- {skylos-2.2.3 → skylos-2.4.0}/pyproject.toml +1 -1
- {skylos-2.2.3 → skylos-2.4.0}/setup.py +1 -1
- {skylos-2.2.3 → skylos-2.4.0}/skylos/__init__.py +1 -1
- {skylos-2.2.3 → skylos-2.4.0}/skylos/analyzer.py +119 -67
- {skylos-2.2.3 → skylos-2.4.0}/skylos/cli.py +172 -10
- skylos-2.4.0/skylos/rules/danger/danger.py +141 -0
- skylos-2.4.0/skylos/rules/danger/danger_cmd/cmd_flow.py +208 -0
- skylos-2.4.0/skylos/rules/danger/danger_fs/path_flow.py +188 -0
- skylos-2.4.0/skylos/rules/danger/danger_net/ssrf_flow.py +198 -0
- skylos-2.4.0/skylos/rules/danger/danger_sql/__init__.py +0 -0
- skylos-2.4.0/skylos/rules/danger/danger_sql/sql_flow.py +175 -0
- skylos-2.4.0/skylos/rules/danger/danger_sql/sql_raw_flow.py +202 -0
- skylos-2.4.0/skylos/rules/danger/danger_web/__init__.py +0 -0
- skylos-2.4.0/skylos/rules/danger/danger_web/xss_flow.py +279 -0
- {skylos-2.2.3 → skylos-2.4.0}/skylos/rules/secrets.py +34 -5
- skylos-2.4.0/skylos/visitors/__init__.py +0 -0
- {skylos-2.2.3 → skylos-2.4.0}/skylos.egg-info/PKG-INFO +1 -1
- {skylos-2.2.3 → skylos-2.4.0}/skylos.egg-info/SOURCES.txt +18 -0
- skylos-2.4.0/test/__init__.py +0 -0
- skylos-2.4.0/test/sample_repo/__init__.py +0 -0
- skylos-2.4.0/test/sample_repo/sample_repo/__init__.py +0 -0
- skylos-2.4.0/test/test_cmd_injection.py +41 -0
- skylos-2.4.0/test/test_dangerous.py +101 -0
- skylos-2.4.0/test/test_path_traversal.py +40 -0
- {skylos-2.2.3 → skylos-2.4.0}/test/test_secrets.py +24 -10
- skylos-2.4.0/test/test_sql_injection.py +54 -0
- skylos-2.4.0/test/test_ssrf.py +51 -0
- {skylos-2.2.3 → skylos-2.4.0}/setup.cfg +0 -0
- {skylos-2.2.3 → skylos-2.4.0}/skylos/codemods.py +0 -0
- {skylos-2.2.3 → skylos-2.4.0}/skylos/constants.py +0 -0
- {skylos-2.2.3 → skylos-2.4.0}/skylos/rules/__init__.py +0 -0
- {skylos-2.2.3/skylos/visitors → skylos-2.4.0/skylos/rules/danger}/__init__.py +0 -0
- {skylos-2.2.3/test → skylos-2.4.0/skylos/rules/danger/danger_cmd}/__init__.py +0 -0
- {skylos-2.2.3/test/sample_repo → skylos-2.4.0/skylos/rules/danger/danger_fs}/__init__.py +0 -0
- {skylos-2.2.3/test/sample_repo/sample_repo → skylos-2.4.0/skylos/rules/danger/danger_net}/__init__.py +0 -0
- {skylos-2.2.3 → skylos-2.4.0}/skylos/server.py +0 -0
- {skylos-2.2.3 → skylos-2.4.0}/skylos/visitor.py +0 -0
- {skylos-2.2.3 → skylos-2.4.0}/skylos/visitors/framework_aware.py +0 -0
- {skylos-2.2.3 → skylos-2.4.0}/skylos/visitors/test_aware.py +0 -0
- {skylos-2.2.3 → skylos-2.4.0}/skylos.egg-info/dependency_links.txt +0 -0
- {skylos-2.2.3 → skylos-2.4.0}/skylos.egg-info/entry_points.txt +0 -0
- {skylos-2.2.3 → skylos-2.4.0}/skylos.egg-info/requires.txt +0 -0
- {skylos-2.2.3 → skylos-2.4.0}/skylos.egg-info/top_level.txt +0 -0
- {skylos-2.2.3 → skylos-2.4.0}/test/compare_tools.py +0 -0
- {skylos-2.2.3 → skylos-2.4.0}/test/conftest.py +0 -0
- {skylos-2.2.3 → skylos-2.4.0}/test/diagnostics.py +0 -0
- {skylos-2.2.3 → skylos-2.4.0}/test/sample_repo/app.py +0 -0
- {skylos-2.2.3 → skylos-2.4.0}/test/sample_repo/sample_repo/commands.py +0 -0
- {skylos-2.2.3 → skylos-2.4.0}/test/sample_repo/sample_repo/models.py +0 -0
- {skylos-2.2.3 → skylos-2.4.0}/test/sample_repo/sample_repo/routes.py +0 -0
- {skylos-2.2.3 → skylos-2.4.0}/test/sample_repo/sample_repo/utils.py +0 -0
- {skylos-2.2.3 → skylos-2.4.0}/test/test_analyzer.py +0 -0
- {skylos-2.2.3 → skylos-2.4.0}/test/test_changes_analyzer.py +0 -0
- {skylos-2.2.3 → skylos-2.4.0}/test/test_cli.py +0 -0
- {skylos-2.2.3 → skylos-2.4.0}/test/test_codemods.py +0 -0
- {skylos-2.2.3 → skylos-2.4.0}/test/test_constants.py +0 -0
- {skylos-2.2.3 → skylos-2.4.0}/test/test_framework_aware.py +0 -0
- {skylos-2.2.3 → skylos-2.4.0}/test/test_integration.py +0 -0
- {skylos-2.2.3 → skylos-2.4.0}/test/test_new_behaviours.py +0 -0
- {skylos-2.2.3 → skylos-2.4.0}/test/test_skylos.py +0 -0
- {skylos-2.2.3 → skylos-2.4.0}/test/test_test_aware.py +0 -0
- {skylos-2.2.3 → skylos-2.4.0}/test/test_visitor.py +0 -0
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|

|
|
5
|
+

|
|
5
6
|

|
|
7
|
+

|
|
6
8
|

|
|
9
|
+

|
|
7
10
|
|
|
8
11
|
<div align="center">
|
|
9
12
|
<img src="assets/SKYLOS.png" alt="Skylos Logo" width="200">
|
|
@@ -25,7 +28,7 @@
|
|
|
25
28
|
- [Web Interface](#web-interface)
|
|
26
29
|
- [Design](#design)
|
|
27
30
|
- [Test File Detection](#test-file-detection)
|
|
28
|
-
- [
|
|
31
|
+
- [Vibe Coding](#vibe-coding)
|
|
29
32
|
- [Ignoring Pragmas](#ignoring-pragmas)
|
|
30
33
|
- [Including & Excluding Files](#including--excluding-files)
|
|
31
34
|
- [CLI Options](#cli-options)
|
|
@@ -33,6 +36,7 @@
|
|
|
33
36
|
- [Interactive Mode](#interactive-mode)
|
|
34
37
|
- [Development](#development)
|
|
35
38
|
- [CI/CD (Pre-commit & GitHub Actions)](#cicd-pre-commit--github-actions)
|
|
39
|
+
- [VS Code extension](#vsc-extension)
|
|
36
40
|
- [FAQ](#faq)
|
|
37
41
|
- [Limitations](#limitations)
|
|
38
42
|
- [Troubleshooting](#troubleshooting)
|
|
@@ -52,8 +56,8 @@
|
|
|
52
56
|
* **Unused Imports**: Identifies imports that are not used
|
|
53
57
|
* **Folder Management**: Inclusion/exclusion of directories
|
|
54
58
|
* **Ignore Pragmas**: Skip lines tagged with `# pragma: no skylos`, `# pragma: no cover`, or `# noqa`
|
|
55
|
-
**NEW** **Secrets Scanning (PoC, opt-in)**: Detects API keys & secrets (GitHub, GitLab, Slack, Stripe, AWS, Google, SendGrid, Twilio, private key blocks)
|
|
56
|
-
|
|
59
|
+
* **NEW** **Secrets Scanning (PoC, opt-in)**: Detects API keys & secrets (GitHub, GitLab, Slack, Stripe, AWS, Google, SendGrid, Twilio, private key blocks)
|
|
60
|
+
* **NEW** **Dangerous Patterns**: Flags risky code such as `eval/exec`, `os.system`, `subprocess(shell=True)`, `pickle.load/loads`, `yaml.load` without SafeLoader, hashlib.md5/sha1. Refer to `DANGEROUS_CODE.md` for the whole list. This includes SQL injection, path traversal and any other security flaws that may arise from the practise of vibe-coding.
|
|
57
61
|
|
|
58
62
|
## Benchmark (You can find this benchmark test in `test` folder)
|
|
59
63
|
|
|
@@ -98,6 +102,7 @@ pip install .
|
|
|
98
102
|
skylos /path/to/your/project
|
|
99
103
|
|
|
100
104
|
skylos /path/to/your/project --secrets ## include api key scan
|
|
105
|
+
skylos /path/to/your/project --danger ## include safety scan for dangerous code
|
|
101
106
|
|
|
102
107
|
# To launch the front end
|
|
103
108
|
skylos run
|
|
@@ -111,8 +116,12 @@ skylos . --interactive --comment-out
|
|
|
111
116
|
# Dry run - see what would be removed
|
|
112
117
|
skylos --interactive --dry-run /path/to/your/project
|
|
113
118
|
|
|
119
|
+
# Load the results in json format
|
|
114
120
|
skylos --json /path/to/your/project
|
|
115
121
|
|
|
122
|
+
# Load the results in table format
|
|
123
|
+
skylos --table /path/to/your/project
|
|
124
|
+
|
|
116
125
|
# With confidence
|
|
117
126
|
skylos path/to/your/file --confidence 20 ## or whatever value u wanna set
|
|
118
127
|
```
|
|
@@ -199,6 +208,49 @@ When Skylos detects a test file, it by default, will apply a confidence penalty
|
|
|
199
208
|
/project/test_data.py
|
|
200
209
|
```
|
|
201
210
|
|
|
211
|
+
## Vibe-Coding
|
|
212
|
+
|
|
213
|
+
We are aware that vibe coding has created a lot of vulnerabilities. To an AI, it's job is to spit out a list of tokens with the highest probability, whether it's right or not. This may introduce vulnerabilities in their applications. We have expanded Skylos to catch the most important problems first.
|
|
214
|
+
|
|
215
|
+
- SQL injection (cursor)
|
|
216
|
+
- SQL injection (raw-API)
|
|
217
|
+
- Command injection
|
|
218
|
+
- SSRF
|
|
219
|
+
- Path traversal
|
|
220
|
+
- eval/exec
|
|
221
|
+
- pickle.load/loads
|
|
222
|
+
- yaml.load w/o SafeLoader
|
|
223
|
+
- weak hashes MD5/SHA1
|
|
224
|
+
- subprocess(..., shell=True)
|
|
225
|
+
- requests(..., verify=False)
|
|
226
|
+
|
|
227
|
+
### Quick Start
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
skylos /path/to/your/project --danger
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Examples that will be flagged
|
|
234
|
+
|
|
235
|
+
```
|
|
236
|
+
# SQLi (cursor)
|
|
237
|
+
cur.execute(f"SELECT * FROM users WHERE name='{name}'")
|
|
238
|
+
|
|
239
|
+
# SQLi (raw-api)
|
|
240
|
+
pd.read_sql("SELECT * FROM users WHERE name='" + q + "'", conn)
|
|
241
|
+
|
|
242
|
+
# Command injection
|
|
243
|
+
os.system("zip -r out.zip " + folder)
|
|
244
|
+
|
|
245
|
+
# SSRF
|
|
246
|
+
requests.get(request.args["url"], timeout=3)
|
|
247
|
+
|
|
248
|
+
# Path traversal
|
|
249
|
+
with open(request.args.get("p"), "r") as f: ...
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
This list will be expanded in the near future. For more information, refer to `DANGEROUS_CODE.md`
|
|
253
|
+
|
|
202
254
|
## Ignoring Pragmas
|
|
203
255
|
|
|
204
256
|
1. To ignore any warning, indicate `# pragma: no skylos` **ON THE SAME LINE** as the function/class you want to ignore
|
|
@@ -251,6 +303,7 @@ Arguments:
|
|
|
251
303
|
Options:
|
|
252
304
|
-h, --help Show this help message and exit
|
|
253
305
|
--json Output raw JSON instead of formatted text
|
|
306
|
+
--table Output results in table format via the CLI
|
|
254
307
|
-o, --output FILE Write output to file instead of stdout
|
|
255
308
|
-v, --verbose Enable verbose output
|
|
256
309
|
--version Checks version
|
|
@@ -262,6 +315,7 @@ Options:
|
|
|
262
315
|
--list-default-excludes List the default excluded folders and
|
|
263
316
|
-c, --confidence LEVEL Confidence threshold (0-100). Lower values will show more items.
|
|
264
317
|
-- secrets Scan for api keys/secrets
|
|
318
|
+
-- danger Scan for dangerous code
|
|
265
319
|
```
|
|
266
320
|
|
|
267
321
|
## Interactive Mode
|
|
@@ -276,22 +330,97 @@ The interactive mode lets you select specific functions and imports to remove:
|
|
|
276
330
|
|
|
277
331
|
Pick **one** (or use **both**)
|
|
278
332
|
|
|
279
|
-
1.
|
|
333
|
+
1. GitHub Actions: runs Skylos on pushes/PRs in CI.
|
|
334
|
+
- No local install needed
|
|
335
|
+
|
|
336
|
+
2. Pre-commit (local + CI): runs Skylos before commits/PRs.
|
|
280
337
|
- You must install pre-commit locally once. Skylos gets installed automatically by the hook.
|
|
281
338
|
|
|
282
|
-
|
|
283
|
-
|
|
339
|
+
### Option A — Github Actions
|
|
340
|
+
|
|
341
|
+
1. Create .github/workflows/skylos.yml **(COPY THE ENTIRE SKYLOS.YAML FROM BELOW)**:
|
|
342
|
+
|
|
343
|
+
```yaml
|
|
344
|
+
name: Skylos Deadcode Scan
|
|
345
|
+
|
|
346
|
+
on:
|
|
347
|
+
pull_request:
|
|
348
|
+
push:
|
|
349
|
+
branches: [ main, master ]
|
|
350
|
+
workflow_dispatch:
|
|
351
|
+
|
|
352
|
+
jobs:
|
|
353
|
+
scan:
|
|
354
|
+
runs-on: ubuntu-latest
|
|
355
|
+
env:
|
|
356
|
+
SKYLOS_STRICT: ${{ vars.SKYLOS_STRICT || 'false' }}
|
|
357
|
+
steps:
|
|
358
|
+
- uses: actions/checkout@v4
|
|
359
|
+
|
|
360
|
+
- uses: actions/setup-python@v5
|
|
361
|
+
with:
|
|
362
|
+
python-version: '3.11'
|
|
363
|
+
cache: 'pip'
|
|
284
364
|
|
|
285
|
-
|
|
365
|
+
- name: Install Skylos
|
|
366
|
+
run: pip install skylos
|
|
286
367
|
|
|
287
|
-
|
|
368
|
+
- name: Run Skylos
|
|
369
|
+
env:
|
|
370
|
+
REPORT: skylos_${{ github.run_number }}_${{ github.sha }}.json
|
|
371
|
+
run: |
|
|
372
|
+
echo "REPORT=$REPORT" >> "$GITHUB_OUTPUT"
|
|
373
|
+
skylos . --json > "$REPORT"
|
|
374
|
+
id: scan
|
|
375
|
+
|
|
376
|
+
- name: Fail if there are findings
|
|
377
|
+
continue-on-error: ${{ env.SKYLOS_STRICT != 'true' }}
|
|
378
|
+
env:
|
|
379
|
+
REPORT: ${{ steps.scan.outputs.REPORT }}
|
|
380
|
+
run: |
|
|
381
|
+
python - << 'PY'
|
|
382
|
+
import json, sys, os
|
|
383
|
+
report = os.environ["REPORT"]
|
|
384
|
+
data = json.load(open(report, "r", encoding="utf-8"))
|
|
385
|
+
count = 0
|
|
386
|
+
for value in data.values():
|
|
387
|
+
if isinstance(value, list):
|
|
388
|
+
count += len(value)
|
|
389
|
+
print(f"Findings: {count}")
|
|
390
|
+
if count > 0:
|
|
391
|
+
print(f"::warning title=Skylos findings::{count} potential issues found. See {report}")
|
|
392
|
+
sys.exit(1 if count > 0 else 0)
|
|
393
|
+
PY
|
|
394
|
+
|
|
395
|
+
- name: Upload report artifact
|
|
396
|
+
if: always()
|
|
397
|
+
uses: actions/upload-artifact@v4
|
|
398
|
+
with:
|
|
399
|
+
name: ${{ steps.scan.outputs.REPORT }}
|
|
400
|
+
path: ${{ steps.scan.outputs.REPORT }}
|
|
401
|
+
|
|
402
|
+
- name: Summarize in job log
|
|
403
|
+
if: always()
|
|
404
|
+
run: |
|
|
405
|
+
echo "Skylos report: ${{ steps.scan.outputs.REPORT }}" >> $GITHUB_STEP_SUMMARY
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
**To make the job fail on findings (strict mode)**:
|
|
409
|
+
|
|
410
|
+
1. Go to GitHub -> Settings -> Secrets and variables -> Actions -> Variables
|
|
411
|
+
|
|
412
|
+
2. Add variable SKYLOS_STRICT with value true
|
|
413
|
+
|
|
414
|
+
### Option B — Pre-commit (local + CI)
|
|
415
|
+
|
|
416
|
+
. Create or edit `.pre-commit-config.yaml` at the repo root:
|
|
288
417
|
|
|
289
418
|
**A: Skylos hook repo**
|
|
290
419
|
```yaml
|
|
291
420
|
## .pre-commit-config.yaml
|
|
292
421
|
repos:
|
|
293
422
|
- repo: https://github.com/duriantaco/skylos
|
|
294
|
-
rev: v2.
|
|
423
|
+
rev: v2.4.0
|
|
295
424
|
hooks:
|
|
296
425
|
- id: skylos-scan
|
|
297
426
|
name: skylos report
|
|
@@ -300,7 +429,7 @@ repos:
|
|
|
300
429
|
types_or: [python]
|
|
301
430
|
pass_filenames: false
|
|
302
431
|
require_serial: true
|
|
303
|
-
args: [".", "--output", "report.json", "--confidence", "70"]
|
|
432
|
+
args: [".", "--output", "report.json", "--confidence", "70", "--danger"]
|
|
304
433
|
|
|
305
434
|
- repo: local
|
|
306
435
|
hooks:
|
|
@@ -341,7 +470,7 @@ repos:
|
|
|
341
470
|
entry: python -m skylos.cli
|
|
342
471
|
pass_filenames: false
|
|
343
472
|
require_serial: true
|
|
344
|
-
additional_dependencies: [skylos==2.
|
|
473
|
+
additional_dependencies: [skylos==2.4.0]
|
|
345
474
|
args: [".", "--output", "report.json", "--confidence", "70"]
|
|
346
475
|
|
|
347
476
|
- id: skylos-fail-on-findings
|
|
@@ -397,80 +526,15 @@ jobs:
|
|
|
397
526
|
|
|
398
527
|
**Pre commit behavior:** the second hook is soft by default (SKYLOS_SOFT=1). This means that it prints findings and passes. You can remove the env/logic if you want pre-commit to block commits on finding
|
|
399
528
|
|
|
400
|
-
|
|
529
|
+
## VS Code extension
|
|
401
530
|
|
|
402
|
-
|
|
531
|
+
Skylos has a VS Code extension that runs on save like a linter. Runs automatically on save of Python files. You will ll see squiggles + a popup like "Skylos found N items." For more information, refer to the VSC `README.md` inside the marketplace.
|
|
403
532
|
|
|
404
|
-
|
|
405
|
-
name: Skylos Deadcode Scan
|
|
533
|
+
### Install
|
|
406
534
|
|
|
407
|
-
|
|
408
|
-
pull_request:
|
|
409
|
-
push:
|
|
410
|
-
branches: [ main, master ]
|
|
411
|
-
workflow_dispatch:
|
|
535
|
+
From VS Code Marketplace: "Skylos" (publisher: oha)
|
|
412
536
|
|
|
413
|
-
|
|
414
|
-
scan:
|
|
415
|
-
runs-on: ubuntu-latest
|
|
416
|
-
env:
|
|
417
|
-
SKYLOS_STRICT: ${{ vars.SKYLOS_STRICT || 'false' }}
|
|
418
|
-
steps:
|
|
419
|
-
- uses: actions/checkout@v4
|
|
420
|
-
|
|
421
|
-
- uses: actions/setup-python@v5
|
|
422
|
-
with:
|
|
423
|
-
python-version: '3.11'
|
|
424
|
-
cache: 'pip'
|
|
425
|
-
|
|
426
|
-
- name: Install Skylos
|
|
427
|
-
run: pip install skylos
|
|
428
|
-
|
|
429
|
-
- name: Run Skylos
|
|
430
|
-
env:
|
|
431
|
-
REPORT: skylos_${{ github.run_number }}_${{ github.sha }}.json
|
|
432
|
-
run: |
|
|
433
|
-
echo "REPORT=$REPORT" >> "$GITHUB_OUTPUT"
|
|
434
|
-
skylos . --json > "$REPORT"
|
|
435
|
-
id: scan
|
|
436
|
-
|
|
437
|
-
- name: Fail if there are findings
|
|
438
|
-
continue-on-error: ${{ env.SKYLOS_STRICT != 'true' }}
|
|
439
|
-
env:
|
|
440
|
-
REPORT: ${{ steps.scan.outputs.REPORT }}
|
|
441
|
-
run: |
|
|
442
|
-
python - << 'PY'
|
|
443
|
-
import json, sys, os
|
|
444
|
-
report = os.environ["REPORT"]
|
|
445
|
-
data = json.load(open(report, "r", encoding="utf-8"))
|
|
446
|
-
count = 0
|
|
447
|
-
for value in data.values():
|
|
448
|
-
if isinstance(value, list):
|
|
449
|
-
count += len(value)
|
|
450
|
-
print(f"Findings: {count}")
|
|
451
|
-
if count > 0:
|
|
452
|
-
print(f"::warning title=Skylos findings::{count} potential issues found. See {report}")
|
|
453
|
-
sys.exit(1 if count > 0 else 0)
|
|
454
|
-
PY
|
|
455
|
-
|
|
456
|
-
- name: Upload report artifact
|
|
457
|
-
if: always()
|
|
458
|
-
uses: actions/upload-artifact@v4
|
|
459
|
-
with:
|
|
460
|
-
name: ${{ steps.scan.outputs.REPORT }}
|
|
461
|
-
path: ${{ steps.scan.outputs.REPORT }}
|
|
462
|
-
|
|
463
|
-
- name: Summarize in job log
|
|
464
|
-
if: always()
|
|
465
|
-
run: |
|
|
466
|
-
echo "Skylos report: ${{ steps.scan.outputs.REPORT }}" >> $GITHUB_STEP_SUMMARY
|
|
467
|
-
```
|
|
468
|
-
|
|
469
|
-
**To make the job fail on findings (strict mode)**:
|
|
470
|
-
|
|
471
|
-
1. Go to GitHub -> Settings -> Secrets and variables -> Actions -> Variables
|
|
472
|
-
|
|
473
|
-
2. Add variable SKYLOS_STRICT with value true
|
|
537
|
+
Version: `0.1.0`
|
|
474
538
|
|
|
475
539
|
## Development
|
|
476
540
|
|
|
@@ -519,6 +583,9 @@ A: Web framework routes are given low confidence (20) because they might be call
|
|
|
519
583
|
**Q: What confidence level should I use?**
|
|
520
584
|
A: Start with 60 (default) for safe cleanup. Use 30 for framework applications. Use 20 for more comprehensive auditing.
|
|
521
585
|
|
|
586
|
+
**Q: What does `--danger` check**?
|
|
587
|
+
A: It flags common security problems. Refer to `DANGEROUS_CODE.md` for the full details
|
|
588
|
+
|
|
522
589
|
## Limitations
|
|
523
590
|
|
|
524
591
|
- **Dynamic code**: `getattr()`, `globals()`, runtime imports are hard to detect
|
|
@@ -562,6 +629,8 @@ We welcome contributions! Please read our [Contributing Guidelines](CONTRIBUTING
|
|
|
562
629
|
- [x] CI/CD integration examples
|
|
563
630
|
- [ ] Further optimization
|
|
564
631
|
- [ ] Add new rules
|
|
632
|
+
- [ ] Expanding on the `dangerous.py` list
|
|
633
|
+
- [ ] Porting to uv
|
|
565
634
|
|
|
566
635
|
## License
|
|
567
636
|
|
|
@@ -9,6 +9,7 @@ from skylos.visitor import Visitor
|
|
|
9
9
|
from skylos.constants import ( PENALTIES, AUTO_CALLED )
|
|
10
10
|
from skylos.visitors.test_aware import TestAwareVisitor
|
|
11
11
|
from skylos.rules.secrets import scan_ctx as _secrets_scan_ctx
|
|
12
|
+
from skylos.rules.danger.danger import scan_ctx as scan_danger
|
|
12
13
|
import os
|
|
13
14
|
import traceback
|
|
14
15
|
from skylos.visitors.framework_aware import FrameworkAwareVisitor, detect_framework_usage
|
|
@@ -238,7 +239,7 @@ class Skylos:
|
|
|
238
239
|
if method.simple_name == "format" and cls.endswith("Formatter"):
|
|
239
240
|
method.references += 1
|
|
240
241
|
|
|
241
|
-
def analyze(self, path, thr=60, exclude_folders= None, enable_secrets = False):
|
|
242
|
+
def analyze(self, path, thr=60, exclude_folders= None, enable_secrets = False, enable_danger = False):
|
|
242
243
|
files, root = self._get_python_files(path, exclude_folders)
|
|
243
244
|
|
|
244
245
|
if not files:
|
|
@@ -262,6 +263,7 @@ class Skylos:
|
|
|
262
263
|
modmap[f] = self._module(root, f)
|
|
263
264
|
|
|
264
265
|
all_secrets = []
|
|
266
|
+
all_dangers = []
|
|
265
267
|
for file in files:
|
|
266
268
|
mod = modmap[file]
|
|
267
269
|
defs, refs, dyn, exports, test_flags, framework_flags = proc_file(file, mod)
|
|
@@ -276,13 +278,25 @@ class Skylos:
|
|
|
276
278
|
|
|
277
279
|
if enable_secrets and _secrets_scan_ctx is not None:
|
|
278
280
|
try:
|
|
279
|
-
|
|
280
|
-
|
|
281
|
+
src = Path(file).read_text(encoding="utf-8", errors="ignore")
|
|
282
|
+
src_lines = src.splitlines(True)
|
|
283
|
+
rel = str(Path(file).relative_to(root))
|
|
284
|
+
ctx = {"relpath": rel, "lines": src_lines, "tree": None}
|
|
281
285
|
findings = list(_secrets_scan_ctx(ctx))
|
|
282
286
|
if findings:
|
|
283
287
|
all_secrets.extend(findings)
|
|
284
288
|
except Exception:
|
|
285
289
|
pass
|
|
290
|
+
|
|
291
|
+
if enable_danger and scan_danger is not None:
|
|
292
|
+
try:
|
|
293
|
+
findings = scan_danger(root, [file])
|
|
294
|
+
if findings:
|
|
295
|
+
all_dangers.extend(findings)
|
|
296
|
+
except Exception as e:
|
|
297
|
+
logger.error(f"Error scanning {file} for dangerous code: {e}")
|
|
298
|
+
if os.getenv("SKYLOS_DEBUG"):
|
|
299
|
+
logger.error(traceback.format_exc())
|
|
286
300
|
|
|
287
301
|
self._mark_refs()
|
|
288
302
|
self._apply_heuristics()
|
|
@@ -296,7 +310,6 @@ class Skylos:
|
|
|
296
310
|
for d in sorted(self.defs.values(), key=def_sort_key):
|
|
297
311
|
if shown >= 50:
|
|
298
312
|
break
|
|
299
|
-
print(f" type={d.type} refs={d.references} conf={d.confidence} exported={d.is_exported} line={d.line} name={d.name}")
|
|
300
313
|
shown += 1
|
|
301
314
|
|
|
302
315
|
unused = []
|
|
@@ -318,7 +331,12 @@ class Skylos:
|
|
|
318
331
|
|
|
319
332
|
if enable_secrets and all_secrets:
|
|
320
333
|
result["secrets"] = all_secrets
|
|
334
|
+
result["analysis_summary"]["secrets_count"] = len(all_secrets)
|
|
321
335
|
|
|
336
|
+
if enable_danger and all_dangers:
|
|
337
|
+
result["danger"] = all_dangers
|
|
338
|
+
result["analysis_summary"]["danger_count"] = len(all_dangers)
|
|
339
|
+
|
|
322
340
|
for u in unused:
|
|
323
341
|
if u["type"] in ("function", "method"):
|
|
324
342
|
result["unused_functions"].append(u)
|
|
@@ -370,70 +388,104 @@ def proc_file(file_or_args, mod=None):
|
|
|
370
388
|
|
|
371
389
|
return [], [], set(), set(), dummy_visitor, dummy_framework_visitor
|
|
372
390
|
|
|
373
|
-
def analyze(path, conf=60, exclude_folders=None, enable_secrets=False):
|
|
374
|
-
return Skylos().analyze(path,conf, exclude_folders, enable_secrets)
|
|
391
|
+
def analyze(path, conf=60, exclude_folders=None, enable_secrets=False, enable_danger=False):
|
|
392
|
+
return Skylos().analyze(path,conf, exclude_folders, enable_secrets, enable_danger)
|
|
375
393
|
|
|
376
394
|
if __name__ == "__main__":
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
print("
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
print("Summary:")
|
|
392
|
-
if data["unused_functions"]:
|
|
393
|
-
print(f" * Unreachable functions: {len(data['unused_functions'])}")
|
|
394
|
-
if data["unused_imports"]:
|
|
395
|
-
print(f" * Unused imports: {len(data['unused_imports'])}")
|
|
396
|
-
if data["unused_classes"]:
|
|
397
|
-
print(f" * Unused classes: {len(data['unused_classes'])}")
|
|
398
|
-
if data["unused_variables"]:
|
|
399
|
-
print(f" * Unused variables: {len(data['unused_variables'])}")
|
|
400
|
-
|
|
401
|
-
if data["unused_functions"]:
|
|
402
|
-
print("\n - Unreachable Functions")
|
|
403
|
-
print("=======================")
|
|
404
|
-
for i, func in enumerate(data["unused_functions"], 1):
|
|
405
|
-
print(f" {i}. {func['name']}")
|
|
406
|
-
print(f" └─ {func['file']}:{func['line']}")
|
|
407
|
-
|
|
408
|
-
if data["unused_imports"]:
|
|
409
|
-
print("\n - Unused Imports")
|
|
410
|
-
print("================")
|
|
411
|
-
for i, imp in enumerate(data["unused_imports"], 1):
|
|
412
|
-
print(f" {i}. {imp['simple_name']}")
|
|
413
|
-
print(f" └─ {imp['file']}:{imp['line']}")
|
|
414
|
-
|
|
415
|
-
if data["unused_classes"]:
|
|
416
|
-
print("\n - Unused Classes")
|
|
417
|
-
print("=================")
|
|
418
|
-
for i, cls in enumerate(data["unused_classes"], 1):
|
|
419
|
-
print(f" {i}. {cls['name']}")
|
|
420
|
-
print(f" └─ {cls['file']}:{cls['line']}")
|
|
421
|
-
|
|
422
|
-
if data["unused_variables"]:
|
|
423
|
-
print("\n - Unused Variables")
|
|
424
|
-
print("==================")
|
|
425
|
-
for i, var in enumerate(data["unused_variables"], 1):
|
|
426
|
-
print(f" {i}. {var['name']}")
|
|
427
|
-
print(f" └─ {var['file']}:{var['line']}")
|
|
428
|
-
|
|
429
|
-
print("\n" + "─" * 50)
|
|
430
|
-
print(f"Found {total_items} dead code items. Add this badge to your README:")
|
|
431
|
-
print(f"```markdown")
|
|
432
|
-
print(f"")
|
|
433
|
-
print(f"```")
|
|
395
|
+
enable_secrets = ("--secrets" in sys.argv)
|
|
396
|
+
enable_danger = ("--danger" in sys.argv)
|
|
397
|
+
|
|
398
|
+
positional = [a for a in sys.argv[1:] if not a.startswith("--")]
|
|
399
|
+
|
|
400
|
+
if not positional:
|
|
401
|
+
print("Usage: python Skylos.py <path> [confidence_threshold] [--secrets] [--danger]")
|
|
402
|
+
sys.exit(2)
|
|
403
|
+
|
|
404
|
+
p = positional[0]
|
|
405
|
+
confidence = int(positional[1]) if len(positional) > 1 else 60
|
|
406
|
+
|
|
407
|
+
result = analyze(p, confidence, enable_secrets=enable_secrets, enable_danger=enable_danger)
|
|
434
408
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
409
|
+
data = json.loads(result)
|
|
410
|
+
print("\n Python Static Analysis Results")
|
|
411
|
+
print("===================================\n")
|
|
412
|
+
|
|
413
|
+
total_dead = 0
|
|
414
|
+
for key, items in data.items():
|
|
415
|
+
if key.startswith("unused_") and isinstance(items, list):
|
|
416
|
+
total_dead += len(items)
|
|
417
|
+
|
|
418
|
+
danger_count = data.get("analysis_summary", {}).get("danger_count", 0) if enable_danger else 0
|
|
419
|
+
secrets_count = data.get("analysis_summary", {}).get("secrets_count", 0) if enable_secrets else 0
|
|
420
|
+
|
|
421
|
+
print("Summary:")
|
|
422
|
+
if data["unused_functions"]:
|
|
423
|
+
print(f" * Unreachable functions: {len(data['unused_functions'])}")
|
|
424
|
+
if data["unused_imports"]:
|
|
425
|
+
print(f" * Unused imports: {len(data['unused_imports'])}")
|
|
426
|
+
if data["unused_classes"]:
|
|
427
|
+
print(f" * Unused classes: {len(data['unused_classes'])}")
|
|
428
|
+
if data["unused_variables"]:
|
|
429
|
+
print(f" * Unused variables: {len(data['unused_variables'])}")
|
|
430
|
+
if enable_danger:
|
|
431
|
+
print(f" * Security issues: {danger_count}")
|
|
432
|
+
if enable_secrets:
|
|
433
|
+
print(f" * Secrets found: {secrets_count}")
|
|
434
|
+
|
|
435
|
+
if data["unused_functions"]:
|
|
436
|
+
print("\n - Unreachable Functions")
|
|
437
|
+
print("=======================")
|
|
438
|
+
for i, func in enumerate(data["unused_functions"], 1):
|
|
439
|
+
print(f" {i}. {func['name']}")
|
|
440
|
+
print(f" └─ {func['file']}:{func['line']}")
|
|
441
|
+
|
|
442
|
+
if data["unused_imports"]:
|
|
443
|
+
print("\n - Unused Imports")
|
|
444
|
+
print("================")
|
|
445
|
+
for i, imp in enumerate(data["unused_imports"], 1):
|
|
446
|
+
print(f" {i}. {imp['simple_name']}")
|
|
447
|
+
print(f" └─ {imp['file']}:{imp['line']}")
|
|
448
|
+
|
|
449
|
+
if data["unused_classes"]:
|
|
450
|
+
print("\n - Unused Classes")
|
|
451
|
+
print("=================")
|
|
452
|
+
for i, cls in enumerate(data["unused_classes"], 1):
|
|
453
|
+
print(f" {i}. {cls['name']}")
|
|
454
|
+
print(f" └─ {cls['file']}:{cls['line']}")
|
|
455
|
+
|
|
456
|
+
if data["unused_variables"]:
|
|
457
|
+
print("\n - Unused Variables")
|
|
458
|
+
print("==================")
|
|
459
|
+
for i, var in enumerate(data["unused_variables"], 1):
|
|
460
|
+
print(f" {i}. {var['name']}")
|
|
461
|
+
print(f" └─ {var['file']}:{var['line']}")
|
|
462
|
+
|
|
463
|
+
if enable_danger and data.get("danger"):
|
|
464
|
+
print("\n - Security Issues")
|
|
465
|
+
print("================")
|
|
466
|
+
for i, f in enumerate(data["danger"], 1):
|
|
467
|
+
print(f" {i}. {f['message']} [{f['rule_id']}] ({f['file']}:{f['line']}) Severity: {f['severity']}")
|
|
468
|
+
|
|
469
|
+
if enable_secrets and data.get("secrets"):
|
|
470
|
+
print("\n - Secrets")
|
|
471
|
+
print("==========")
|
|
472
|
+
for i, s in enumerate(data["secrets"], 1):
|
|
473
|
+
rid = s.get("rule_id", "SECRET")
|
|
474
|
+
msg = s.get("message", "Potential secret")
|
|
475
|
+
file = s.get("file")
|
|
476
|
+
line = s.get("line", 1)
|
|
477
|
+
sev = s.get("severity", "HIGH")
|
|
478
|
+
print(f" {i}. {msg} [{rid}] ({file}:{line}) Severity: {sev}")
|
|
479
|
+
|
|
480
|
+
print("\n" + "─" * 50)
|
|
481
|
+
if enable_danger:
|
|
482
|
+
print(f"Found {total_dead} dead code items and {danger_count} security flaws. Add this badge to your README:")
|
|
438
483
|
else:
|
|
439
|
-
print("
|
|
484
|
+
print(f"Found {total_dead} dead code items. Add this badge to your README:")
|
|
485
|
+
print("```markdown")
|
|
486
|
+
print(f"")
|
|
487
|
+
print("```")
|
|
488
|
+
|
|
489
|
+
print("\nNext steps:")
|
|
490
|
+
print(" * Use --interactive to select specific items to remove")
|
|
491
|
+
print(" * Use --dry-run to preview changes before applying them")
|