skill-scanner 0.1.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.
Files changed (51) hide show
  1. skill_scanner-0.1.0/.github/workflows/ci.yml +64 -0
  2. skill_scanner-0.1.0/.github/workflows/publish-testpypi.yml +49 -0
  3. skill_scanner-0.1.0/.github/workflows/release.yml +39 -0
  4. skill_scanner-0.1.0/.github/workflows/zizmor.yml +28 -0
  5. skill_scanner-0.1.0/.gitignore +18 -0
  6. skill_scanner-0.1.0/.python-version +1 -0
  7. skill_scanner-0.1.0/PKG-INFO +156 -0
  8. skill_scanner-0.1.0/README.md +136 -0
  9. skill_scanner-0.1.0/pyproject.toml +58 -0
  10. skill_scanner-0.1.0/renovate.json +75 -0
  11. skill_scanner-0.1.0/src/skill_scanner/__init__.py +4 -0
  12. skill_scanner-0.1.0/src/skill_scanner/__main__.py +6 -0
  13. skill_scanner-0.1.0/src/skill_scanner/analyzers/__init__.py +5 -0
  14. skill_scanner-0.1.0/src/skill_scanner/analyzers/ai_analyzer.py +46 -0
  15. skill_scanner-0.1.0/src/skill_scanner/analyzers/pipeline.py +104 -0
  16. skill_scanner-0.1.0/src/skill_scanner/analyzers/vt_analyzer.py +114 -0
  17. skill_scanner-0.1.0/src/skill_scanner/cli.py +229 -0
  18. skill_scanner-0.1.0/src/skill_scanner/config.py +58 -0
  19. skill_scanner-0.1.0/src/skill_scanner/discovery/__init__.py +5 -0
  20. skill_scanner-0.1.0/src/skill_scanner/discovery/finder.py +356 -0
  21. skill_scanner-0.1.0/src/skill_scanner/discovery/patterns.py +62 -0
  22. skill_scanner-0.1.0/src/skill_scanner/models/__init__.py +21 -0
  23. skill_scanner-0.1.0/src/skill_scanner/models/findings.py +38 -0
  24. skill_scanner-0.1.0/src/skill_scanner/models/reports.py +49 -0
  25. skill_scanner-0.1.0/src/skill_scanner/models/targets.py +55 -0
  26. skill_scanner-0.1.0/src/skill_scanner/output/__init__.py +14 -0
  27. skill_scanner-0.1.0/src/skill_scanner/output/console.py +36 -0
  28. skill_scanner-0.1.0/src/skill_scanner/output/json_export.py +12 -0
  29. skill_scanner-0.1.0/src/skill_scanner/output/sarif_export.py +66 -0
  30. skill_scanner-0.1.0/src/skill_scanner/output/summary.py +110 -0
  31. skill_scanner-0.1.0/src/skill_scanner/providers/__init__.py +17 -0
  32. skill_scanner-0.1.0/src/skill_scanner/providers/base.py +41 -0
  33. skill_scanner-0.1.0/src/skill_scanner/providers/openai_provider.py +86 -0
  34. skill_scanner-0.1.0/src/skill_scanner/scoring/__init__.py +5 -0
  35. skill_scanner-0.1.0/src/skill_scanner/scoring/risk.py +60 -0
  36. skill_scanner-0.1.0/src/skill_scanner/utils/retry.py +34 -0
  37. skill_scanner-0.1.0/src/skill_scanner/validation/__init__.py +7 -0
  38. skill_scanner-0.1.0/src/skill_scanner/validation/frontmatter.py +24 -0
  39. skill_scanner-0.1.0/src/skill_scanner/validation/skill_spec.py +105 -0
  40. skill_scanner-0.1.0/src/skill_scanner/validation/static_rules.py +77 -0
  41. skill_scanner-0.1.0/tests/conftest.py +10 -0
  42. skill_scanner-0.1.0/tests/fixtures/encoded_payload_skill/scripts/run.sh +1 -0
  43. skill_scanner-0.1.0/tests/fixtures/exfil_skill/scripts/exfil.py +3 -0
  44. skill_scanner-0.1.0/tests/test_ai_analyzer.py +71 -0
  45. skill_scanner-0.1.0/tests/test_cli.py +187 -0
  46. skill_scanner-0.1.0/tests/test_discovery.py +46 -0
  47. skill_scanner-0.1.0/tests/test_pipeline.py +65 -0
  48. skill_scanner-0.1.0/tests/test_scoring.py +62 -0
  49. skill_scanner-0.1.0/tests/test_validation.py +50 -0
  50. skill_scanner-0.1.0/tests/test_vt_analyzer.py +68 -0
  51. skill_scanner-0.1.0/uv.lock +1520 -0
@@ -0,0 +1,64 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ paths:
7
+ - "src/**"
8
+ - "tests/**"
9
+ - "pyproject.toml"
10
+ - "uv.lock"
11
+ - ".github/workflows/**"
12
+ pull_request:
13
+ paths:
14
+ - "src/**"
15
+ - "tests/**"
16
+ - "pyproject.toml"
17
+ - "uv.lock"
18
+ - ".github/workflows/**"
19
+
20
+ concurrency:
21
+ group: ${{ github.workflow }}-${{ github.ref }}
22
+ cancel-in-progress: true
23
+
24
+ permissions:
25
+ contents: read
26
+
27
+ jobs:
28
+ trusted-publishing-config:
29
+ runs-on: ubuntu-latest
30
+ steps:
31
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
32
+ with:
33
+ persist-credentials: false
34
+ - name: Validate trusted publishing workflows
35
+ run: |
36
+ set -euo pipefail
37
+ test -f .github/workflows/publish-testpypi.yml
38
+ test -f .github/workflows/release.yml
39
+ grep -Fq 'name: test' .github/workflows/publish-testpypi.yml
40
+ grep -Fq 'name: release' .github/workflows/release.yml
41
+ grep -Fq 'id-token: write' .github/workflows/publish-testpypi.yml
42
+ grep -Fq 'id-token: write' .github/workflows/release.yml
43
+ grep -Fq 'gh-action-pypi-publish@' .github/workflows/publish-testpypi.yml
44
+ grep -Fq 'gh-action-pypi-publish@' .github/workflows/release.yml
45
+ grep -Fq 'repository-url: https://test.pypi.org/legacy/' .github/workflows/publish-testpypi.yml
46
+
47
+ test:
48
+ needs: trusted-publishing-config
49
+ runs-on: ubuntu-latest
50
+ strategy:
51
+ matrix:
52
+ python-version: ["3.11", "3.12", "3.13"]
53
+ steps:
54
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
55
+ with:
56
+ persist-credentials: false
57
+ - uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
58
+ with:
59
+ python-version: ${{ matrix.python-version }}
60
+ enable-cache: true
61
+ - run: uv sync --locked --extra all --group dev
62
+ - run: uv run ruff check .
63
+ - run: uv run mypy src
64
+ - run: uv run pytest --cov=src --cov-report=xml
@@ -0,0 +1,49 @@
1
+ name: Publish TestPyPI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ paths:
7
+ - "src/**"
8
+ - "pyproject.toml"
9
+ - "uv.lock"
10
+ - "README.md"
11
+ - ".github/workflows/publish-testpypi.yml"
12
+ - ".github/workflows/release.yml"
13
+ workflow_dispatch:
14
+
15
+ concurrency:
16
+ group: ${{ github.workflow }}-${{ github.ref }}
17
+ cancel-in-progress: true
18
+
19
+ permissions:
20
+ contents: read
21
+
22
+ jobs:
23
+ test-pypi-publish:
24
+ name: Publish to TestPyPI
25
+ runs-on: ubuntu-latest
26
+ environment:
27
+ name: test
28
+ url: https://test.pypi.org/p/skill-scanner
29
+ permissions:
30
+ id-token: write
31
+ attestations: write
32
+ contents: read
33
+ steps:
34
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
35
+ with:
36
+ persist-credentials: false
37
+ - uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
38
+ with:
39
+ python-version: "3.12"
40
+ enable-cache: false
41
+ - run: uv sync --locked --group dev
42
+ - run: uv build --no-sources
43
+ - name: Attest build provenance
44
+ uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3
45
+ with:
46
+ subject-path: "dist/*"
47
+ - uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
48
+ with:
49
+ repository-url: https://test.pypi.org/legacy/
@@ -0,0 +1,39 @@
1
+ name: Publish PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ concurrency:
8
+ group: ${{ github.workflow }}-${{ github.ref }}
9
+ cancel-in-progress: false
10
+
11
+ permissions:
12
+ contents: read
13
+
14
+ jobs:
15
+ pypi-publish:
16
+ name: Publish to PyPI
17
+ runs-on: ubuntu-latest
18
+ environment:
19
+ name: release
20
+ url: https://pypi.org/p/skill-scanner
21
+ permissions:
22
+ id-token: write
23
+ attestations: write
24
+ contents: read
25
+ steps:
26
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
27
+ with:
28
+ persist-credentials: false
29
+ - uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
30
+ with:
31
+ python-version: "3.12"
32
+ enable-cache: false
33
+ - run: uv sync --locked --group dev
34
+ - run: uv build --no-sources
35
+ - name: Attest build provenance
36
+ uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3
37
+ with:
38
+ subject-path: "dist/*"
39
+ - uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
@@ -0,0 +1,28 @@
1
+ name: GitHub Actions Security Analysis with zizmor
2
+
3
+ on:
4
+ push:
5
+ branches: ["main"]
6
+ pull_request:
7
+ branches: ["**"]
8
+
9
+ concurrency:
10
+ group: ${{ github.workflow }}-${{ github.ref }}
11
+ cancel-in-progress: true
12
+
13
+ permissions: {}
14
+
15
+ jobs:
16
+ zizmor:
17
+ name: Run zizmor
18
+ runs-on: ubuntu-latest
19
+ permissions:
20
+ security-events: write
21
+ steps:
22
+ - name: Checkout repository
23
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
24
+ with:
25
+ persist-credentials: false
26
+
27
+ - name: Run zizmor
28
+ uses: zizmorcore/zizmor-action@135698455da5c3b3e55f73f4419e481ab68cdd95 # v0.4.1
@@ -0,0 +1,18 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .pytest_cache/
4
+ .mypy_cache/
5
+ .ruff_cache/
6
+ .coverage
7
+ coverage.xml
8
+ htmlcov/
9
+ .venv/
10
+ dist/
11
+ build/
12
+ *.egg-info/
13
+ .DS_Store
14
+ .cache/
15
+ reports/
16
+ .env
17
+
18
+ SKILL.md
@@ -0,0 +1 @@
1
+ 3.11
@@ -0,0 +1,156 @@
1
+ Metadata-Version: 2.4
2
+ Name: skill-scanner
3
+ Version: 0.1.0
4
+ Summary: Security scanner for AI agent skills and instruction artifacts
5
+ Author: skill-scanner
6
+ Requires-Python: >=3.11
7
+ Requires-Dist: httpx==0.28.1
8
+ Requires-Dist: pydantic==2.12.5
9
+ Requires-Dist: pyyaml==6.0.3
10
+ Requires-Dist: rich==14.3.2
11
+ Requires-Dist: typer==0.24.0
12
+ Provides-Extra: all
13
+ Requires-Dist: openai==2.21.0; extra == 'all'
14
+ Requires-Dist: vt-py==0.22.0; extra == 'all'
15
+ Provides-Extra: openai
16
+ Requires-Dist: openai==2.21.0; extra == 'openai'
17
+ Provides-Extra: virustotal
18
+ Requires-Dist: vt-py==0.22.0; extra == 'virustotal'
19
+ Description-Content-Type: text/markdown
20
+
21
+ # skill-scanner
22
+
23
+ `skill-scanner` reviews AI skill and instruction artifacts for security risk using:
24
+ - OpenAI analysis
25
+ - VirusTotal analysis
26
+
27
+ ## Requirements
28
+
29
+ - Python 3.11+
30
+ - [`uv`](https://docs.astral.sh/uv/)
31
+ - OpenAI and/or VirusTotal API key (at least one)
32
+
33
+ ## Install (from source)
34
+
35
+ ```bash
36
+ uv sync --all-extras --group dev
37
+ ```
38
+
39
+ Run with:
40
+
41
+ ```bash
42
+ uv run skill-scanner --help
43
+ ```
44
+
45
+ Alias:
46
+
47
+ ```bash
48
+ uv run skillscan --help
49
+ ```
50
+
51
+ ## What gets scanned
52
+
53
+ By default, `discover` and `scan` detect common skill/instruction files (for example `SKILL.md`, `AGENTS.md`, `*.instructions.md`, `*.prompt.md`, `.mdc`, and related artifacts).
54
+
55
+ Use `--path` to target a specific file or folder.
56
+
57
+ ## Quick start
58
+
59
+ ```bash
60
+ # See targets
61
+ uv run skill-scanner discover --format json
62
+
63
+ # Verify key/model configuration
64
+ uv run skill-scanner doctor
65
+
66
+ # Run a combined scan (if both keys are configured)
67
+ uv run skill-scanner scan --format summary
68
+ ```
69
+
70
+ ## Key configuration and analyzer selection
71
+
72
+ `scan` requires at least one analyzer enabled.
73
+
74
+ - If only `OPENAI_API_KEY` is available, AI runs and VT is disabled.
75
+ - If only `VT_API_KEY` is available, VT runs and AI is disabled.
76
+ - If both keys are available, VT findings are included and VT context is passed into AI analysis.
77
+ - You can disable either analyzer with `--no-ai` or `--no-vt`.
78
+
79
+ ## API key safety
80
+
81
+ Never commit API keys. This repository ignores `.env` by default.
82
+
83
+ ### Option 1: Shell environment variables
84
+
85
+ ```bash
86
+ export OPENAI_API_KEY="..."
87
+ export VT_API_KEY="..."
88
+ uv run skill-scanner scan --format summary
89
+ ```
90
+
91
+ ### Option 2: Local `.env` file
92
+
93
+ ```bash
94
+ OPENAI_API_KEY=...
95
+ VT_API_KEY=...
96
+ ```
97
+
98
+ Then run:
99
+
100
+ ```bash
101
+ uv run skill-scanner doctor
102
+ uv run skill-scanner scan --format summary
103
+ ```
104
+
105
+ ### Option 3: 1Password secret references (recommended)
106
+
107
+ Use 1Password secret references instead of plaintext secrets in `.env`:
108
+
109
+ ```bash
110
+ OPENAI_API_KEY=op://Engineering/OpenAI/api_key
111
+ VT_API_KEY=op://Engineering/VirusTotal/api_key
112
+ ```
113
+
114
+ Run the scanner through 1Password CLI so references are resolved at runtime:
115
+
116
+ ```bash
117
+ op run --env-file=.env -- uv run skill-scanner scan --format summary
118
+ ```
119
+
120
+ Security best practice:
121
+ - Prefer a 1Password Service Account scoped to only the vault/items required for scanning (least privilege).
122
+
123
+ Reference:
124
+ - https://developer.1password.com/docs/cli/secret-references/
125
+
126
+ ## Output formats
127
+
128
+ `scan --format` supports:
129
+ - `table` (default)
130
+ - `summary`
131
+ - `json`
132
+ - `sarif`
133
+
134
+ You can write output to a file with `--output <path>`.
135
+
136
+ ## Useful commands
137
+
138
+ ```bash
139
+ # List providers
140
+ uv run skill-scanner providers
141
+
142
+ # Scan one path only
143
+ uv run skill-scanner scan --path ./some/skill/folder --format summary
144
+
145
+ # Filter to medium+
146
+ uv run skill-scanner scan --min-severity medium --format summary
147
+
148
+ # Non-zero exit if high+ findings exist
149
+ uv run skill-scanner scan --fail-on high --format summary
150
+ ```
151
+
152
+ ## Exit behavior
153
+
154
+ - `0`: scan completed and fail threshold not hit
155
+ - `1`: `--fail-on` threshold matched
156
+ - `2`: no analyzers enabled (for example missing keys combined with flags)
@@ -0,0 +1,136 @@
1
+ # skill-scanner
2
+
3
+ `skill-scanner` reviews AI skill and instruction artifacts for security risk using:
4
+ - OpenAI analysis
5
+ - VirusTotal analysis
6
+
7
+ ## Requirements
8
+
9
+ - Python 3.11+
10
+ - [`uv`](https://docs.astral.sh/uv/)
11
+ - OpenAI and/or VirusTotal API key (at least one)
12
+
13
+ ## Install (from source)
14
+
15
+ ```bash
16
+ uv sync --all-extras --group dev
17
+ ```
18
+
19
+ Run with:
20
+
21
+ ```bash
22
+ uv run skill-scanner --help
23
+ ```
24
+
25
+ Alias:
26
+
27
+ ```bash
28
+ uv run skillscan --help
29
+ ```
30
+
31
+ ## What gets scanned
32
+
33
+ By default, `discover` and `scan` detect common skill/instruction files (for example `SKILL.md`, `AGENTS.md`, `*.instructions.md`, `*.prompt.md`, `.mdc`, and related artifacts).
34
+
35
+ Use `--path` to target a specific file or folder.
36
+
37
+ ## Quick start
38
+
39
+ ```bash
40
+ # See targets
41
+ uv run skill-scanner discover --format json
42
+
43
+ # Verify key/model configuration
44
+ uv run skill-scanner doctor
45
+
46
+ # Run a combined scan (if both keys are configured)
47
+ uv run skill-scanner scan --format summary
48
+ ```
49
+
50
+ ## Key configuration and analyzer selection
51
+
52
+ `scan` requires at least one analyzer enabled.
53
+
54
+ - If only `OPENAI_API_KEY` is available, AI runs and VT is disabled.
55
+ - If only `VT_API_KEY` is available, VT runs and AI is disabled.
56
+ - If both keys are available, VT findings are included and VT context is passed into AI analysis.
57
+ - You can disable either analyzer with `--no-ai` or `--no-vt`.
58
+
59
+ ## API key safety
60
+
61
+ Never commit API keys. This repository ignores `.env` by default.
62
+
63
+ ### Option 1: Shell environment variables
64
+
65
+ ```bash
66
+ export OPENAI_API_KEY="..."
67
+ export VT_API_KEY="..."
68
+ uv run skill-scanner scan --format summary
69
+ ```
70
+
71
+ ### Option 2: Local `.env` file
72
+
73
+ ```bash
74
+ OPENAI_API_KEY=...
75
+ VT_API_KEY=...
76
+ ```
77
+
78
+ Then run:
79
+
80
+ ```bash
81
+ uv run skill-scanner doctor
82
+ uv run skill-scanner scan --format summary
83
+ ```
84
+
85
+ ### Option 3: 1Password secret references (recommended)
86
+
87
+ Use 1Password secret references instead of plaintext secrets in `.env`:
88
+
89
+ ```bash
90
+ OPENAI_API_KEY=op://Engineering/OpenAI/api_key
91
+ VT_API_KEY=op://Engineering/VirusTotal/api_key
92
+ ```
93
+
94
+ Run the scanner through 1Password CLI so references are resolved at runtime:
95
+
96
+ ```bash
97
+ op run --env-file=.env -- uv run skill-scanner scan --format summary
98
+ ```
99
+
100
+ Security best practice:
101
+ - Prefer a 1Password Service Account scoped to only the vault/items required for scanning (least privilege).
102
+
103
+ Reference:
104
+ - https://developer.1password.com/docs/cli/secret-references/
105
+
106
+ ## Output formats
107
+
108
+ `scan --format` supports:
109
+ - `table` (default)
110
+ - `summary`
111
+ - `json`
112
+ - `sarif`
113
+
114
+ You can write output to a file with `--output <path>`.
115
+
116
+ ## Useful commands
117
+
118
+ ```bash
119
+ # List providers
120
+ uv run skill-scanner providers
121
+
122
+ # Scan one path only
123
+ uv run skill-scanner scan --path ./some/skill/folder --format summary
124
+
125
+ # Filter to medium+
126
+ uv run skill-scanner scan --min-severity medium --format summary
127
+
128
+ # Non-zero exit if high+ findings exist
129
+ uv run skill-scanner scan --fail-on high --format summary
130
+ ```
131
+
132
+ ## Exit behavior
133
+
134
+ - `0`: scan completed and fail threshold not hit
135
+ - `1`: `--fail-on` threshold matched
136
+ - `2`: no analyzers enabled (for example missing keys combined with flags)
@@ -0,0 +1,58 @@
1
+ [project]
2
+ name = "skill-scanner"
3
+ version = "0.1.0"
4
+ description = "Security scanner for AI agent skills and instruction artifacts"
5
+ readme = "README.md"
6
+ authors = [{ name = "skill-scanner" }]
7
+ requires-python = ">=3.11"
8
+ dependencies = [
9
+ "httpx==0.28.1",
10
+ "pydantic==2.12.5",
11
+ "pyyaml==6.0.3",
12
+ "rich==14.3.2",
13
+ "typer==0.24.0",
14
+ ]
15
+
16
+ [project.optional-dependencies]
17
+ openai = ["openai==2.21.0"]
18
+ virustotal = ["vt-py==0.22.0"]
19
+ all = ["openai==2.21.0", "vt-py==0.22.0"]
20
+
21
+ [dependency-groups]
22
+ dev = [
23
+ "mypy==1.19.1",
24
+ "pytest==9.0.2",
25
+ "pytest-cov==7.0.0",
26
+ "ruff==0.15.1",
27
+ "types-PyYAML==6.0.12.20250915",
28
+ ]
29
+
30
+ [project.scripts]
31
+ skill-scanner = "skill_scanner.cli:app"
32
+ skillscan = "skill_scanner.cli:app"
33
+
34
+ [build-system]
35
+ requires = ["hatchling==1.27.0"]
36
+ build-backend = "hatchling.build"
37
+
38
+ [tool.hatch.build.targets.wheel]
39
+ packages = ["src/skill_scanner"]
40
+
41
+ [tool.ruff]
42
+ line-length = 100
43
+ target-version = "py311"
44
+ src = ["src", "tests"]
45
+
46
+ [tool.ruff.lint]
47
+ select = ["E", "F", "I", "UP", "B", "SIM"]
48
+ ignore = ["B008", "E501", "SIM108", "SIM115"]
49
+
50
+ [tool.mypy]
51
+ python_version = "3.11"
52
+ strict = true
53
+ warn_unused_configs = true
54
+ mypy_path = "src"
55
+
56
+ [tool.pytest.ini_options]
57
+ testpaths = ["tests"]
58
+ addopts = "-q"
@@ -0,0 +1,75 @@
1
+ {
2
+ "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3
+ "extends": [
4
+ "config:best-practices",
5
+ "helpers:pinGitHubActionDigests",
6
+ "helpers:pinGitHubActionDigestsToSemver",
7
+ "schedule:weekly"
8
+ ],
9
+ "dependencyDashboard": true,
10
+ "minimumReleaseAge": "7 days",
11
+ "rangeStrategy": "pin",
12
+ "lockFileMaintenance": {
13
+ "enabled": true,
14
+ "schedule": [
15
+ "before 5am on monday"
16
+ ]
17
+ },
18
+ "packageRules": [
19
+ {
20
+ "groupName": "github-actions",
21
+ "matchManagers": [
22
+ "github-actions"
23
+ ]
24
+ },
25
+ {
26
+ "groupName": "Testing Framework",
27
+ "matchDatasources": [
28
+ "pypi"
29
+ ],
30
+ "matchPackageNames": [
31
+ "pytest",
32
+ "/^pytest-/"
33
+ ]
34
+ },
35
+ {
36
+ "groupName": "Type Checking & Validation",
37
+ "matchDatasources": [
38
+ "pypi"
39
+ ],
40
+ "matchPackageNames": [
41
+ "mypy",
42
+ "types-PyYAML",
43
+ "pydantic",
44
+ "pydantic-core"
45
+ ]
46
+ },
47
+ {
48
+ "groupName": "Dev Tools",
49
+ "matchDatasources": [
50
+ "pypi"
51
+ ],
52
+ "matchPackageNames": [
53
+ "ruff"
54
+ ]
55
+ },
56
+ {
57
+ "groupName": "Runtime Dependencies",
58
+ "matchDatasources": [
59
+ "pypi"
60
+ ],
61
+ "matchPackageNames": [
62
+ "httpx",
63
+ "openai",
64
+ "typer",
65
+ "vt-py"
66
+ ]
67
+ },
68
+ {
69
+ "matchUpdateTypes": [
70
+ "major"
71
+ ],
72
+ "dependencyDashboardApproval": true
73
+ }
74
+ ]
75
+ }
@@ -0,0 +1,4 @@
1
+ """skill_scanner package."""
2
+
3
+ __all__ = ["__version__"]
4
+ __version__ = "0.1.0"
@@ -0,0 +1,6 @@
1
+ """Module entry point for python -m skill_scanner."""
2
+
3
+ from skill_scanner.cli import app
4
+
5
+ if __name__ == "__main__":
6
+ app()
@@ -0,0 +1,5 @@
1
+ """Analyzers package."""
2
+
3
+ from skill_scanner.analyzers.pipeline import run_scan
4
+
5
+ __all__ = ["run_scan"]
@@ -0,0 +1,46 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+ from skill_scanner.models.reports import AIReport, VTReport
6
+ from skill_scanner.models.targets import ScanTarget
7
+ from skill_scanner.providers.base import LLMProvider
8
+
9
+
10
+ def build_payload(target: ScanTarget, max_chars: int = 400_000) -> str:
11
+ chunks: list[str] = []
12
+ total = 0
13
+ for meta in target.files:
14
+ path = Path(meta.path)
15
+ try:
16
+ text = path.read_text(encoding="utf-8", errors="ignore")
17
+ except Exception:
18
+ continue
19
+ chunk = f"\n## FILE: {meta.relative_path}\n{text}\n"
20
+ if total + len(chunk) > max_chars:
21
+ continue
22
+ chunks.append(chunk)
23
+ total += len(chunk)
24
+ return "".join(chunks)
25
+
26
+
27
+ def _append_vt_context(payload: str, vt_report: VTReport | None) -> str:
28
+ if vt_report is None:
29
+ return payload
30
+ vt_context = (
31
+ "\n## VIRUSTOTAL_CONTEXT\n"
32
+ f"sha256: {vt_report.sha256}\n"
33
+ f"malicious: {vt_report.malicious}\n"
34
+ f"suspicious: {vt_report.suspicious}\n"
35
+ f"harmless: {vt_report.harmless}\n"
36
+ f"undetected: {vt_report.undetected}\n"
37
+ f"permalink: {vt_report.permalink or 'n/a'}\n"
38
+ )
39
+ return f"{payload}{vt_context}"
40
+
41
+
42
+ def analyze_with_ai(target: ScanTarget, provider: LLMProvider, vt_report: VTReport | None = None) -> AIReport:
43
+ payload = build_payload(target)
44
+ if not payload.strip():
45
+ return AIReport(provider=provider.name, model=provider.model, findings=[])
46
+ return provider.analyze(target, _append_vt_context(payload, vt_report))