skill-sentry-cli 2.0.0__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.
- skill_sentry_cli/__init__.py +5 -0
- skill_sentry_cli/__main__.py +4 -0
- skill_sentry_cli/cli.py +918 -0
- skill_sentry_cli/core/__init__.py +1 -0
- skill_sentry_cli/core/anchors/__init__.py +5 -0
- skill_sentry_cli/core/anchors/credential_flow.py +440 -0
- skill_sentry_cli/core/anchors/declared_hosts.py +78 -0
- skill_sentry_cli/core/cache/__init__.py +35 -0
- skill_sentry_cli/core/cache/hash_utils.py +206 -0
- skill_sentry_cli/core/cache/sqlite_store.py +925 -0
- skill_sentry_cli/core/config.py +299 -0
- skill_sentry_cli/core/contracts.py +551 -0
- skill_sentry_cli/core/decisions/__init__.py +44 -0
- skill_sentry_cli/core/decisions/base.py +174 -0
- skill_sentry_cli/core/decisions/calibration.py +325 -0
- skill_sentry_cli/core/decisions/laya.py +439 -0
- skill_sentry_cli/core/decisions/mapping.py +218 -0
- skill_sentry_cli/core/decisions/mock.py +118 -0
- skill_sentry_cli/core/decisions/questions.py +118 -0
- skill_sentry_cli/core/decisions/report.py +65 -0
- skill_sentry_cli/core/decisions/selector.py +151 -0
- skill_sentry_cli/core/engine.py +1412 -0
- skill_sentry_cli/core/evasion/__init__.py +27 -0
- skill_sentry_cli/core/evasion/detector.py +210 -0
- skill_sentry_cli/core/exit_codes.py +13 -0
- skill_sentry_cli/core/models/__init__.py +20 -0
- skill_sentry_cli/core/models/_json.py +147 -0
- skill_sentry_cli/core/models/base.py +50 -0
- skill_sentry_cli/core/models/gemma_cloud.py +240 -0
- skill_sentry_cli/core/models/gemma_local.py +273 -0
- skill_sentry_cli/core/models/image_utils.py +112 -0
- skill_sentry_cli/core/models/mock.py +152 -0
- skill_sentry_cli/core/models/selector.py +123 -0
- skill_sentry_cli/core/parser/__init__.py +29 -0
- skill_sentry_cli/core/parser/skill_parser.py +520 -0
- skill_sentry_cli/core/parser/typosquat.py +167 -0
- skill_sentry_cli/core/poisoned_well/__init__.py +52 -0
- skill_sentry_cli/core/poisoned_well/attacker_profile.py +230 -0
- skill_sentry_cli/core/poisoned_well/canary_gen.py +413 -0
- skill_sentry_cli/core/poisoned_well/exfil_tracker.py +470 -0
- skill_sentry_cli/core/reports.py +114 -0
- skill_sentry_cli/core/sandbox/__init__.py +74 -0
- skill_sentry_cli/core/sandbox/assets/Dockerfile.sandbox +39 -0
- skill_sentry_cli/core/sandbox/assets/masquerade.c +484 -0
- skill_sentry_cli/core/sandbox/assets/run_skill.sh +41 -0
- skill_sentry_cli/core/sandbox/assets/sinkhole_sidecar.py +171 -0
- skill_sentry_cli/core/sandbox/canary_fs.py +108 -0
- skill_sentry_cli/core/sandbox/context_honeypot.py +97 -0
- skill_sentry_cli/core/sandbox/dns_sinkhole.py +218 -0
- skill_sentry_cli/core/sandbox/fake_http.py +194 -0
- skill_sentry_cli/core/sandbox/instructions.py +170 -0
- skill_sentry_cli/core/sandbox/masquerade.py +265 -0
- skill_sentry_cli/core/sandbox/monitor.py +278 -0
- skill_sentry_cli/core/sandbox/runner.py +776 -0
- skill_sentry_cli/core/sandbox/sidecar.py +327 -0
- skill_sentry_cli/core/semantic/__init__.py +9 -0
- skill_sentry_cli/core/semantic/analyzer.py +443 -0
- skill_sentry_cli/core/semantic/ast_anchor.py +371 -0
- skill_sentry_cli/core/semantic/ast_python.py +156 -0
- skill_sentry_cli/core/semantic/candidates.py +78 -0
- skill_sentry_cli/core/semantic/laya_analyzer.py +241 -0
- skill_sentry_cli/core/semantic/prompts.py +61 -0
- skill_sentry_cli/core/verdict/__init__.py +24 -0
- skill_sentry_cli/core/verdict/fusion.py +392 -0
- skill_sentry_cli/core/verdict/markdown_report.py +211 -0
- skill_sentry_cli/core/verdict/sarif_report.py +139 -0
- skill_sentry_cli/core/verdict/status.py +35 -0
- skill_sentry_cli/core/verdict/stix_report.py +132 -0
- skill_sentry_cli/core/version.py +38 -0
- skill_sentry_cli/core/vision/__init__.py +8 -0
- skill_sentry_cli/core/vision/image_extractor.py +91 -0
- skill_sentry_cli/core/vision/vision_scanner.py +274 -0
- skill_sentry_cli/data/__init__.py +1 -0
- skill_sentry_cli/data/known_skills_v1.json +276 -0
- skill_sentry_cli/data/sandbox_image.json +4 -0
- skill_sentry_cli/rules/credential_rules.toml +94 -0
- skill_sentry_cli/server/__init__.py +5 -0
- skill_sentry_cli/server/app.py +129 -0
- skill_sentry_cli/server/backend.py +63 -0
- skill_sentry_cli/server/demo_assets.py +62 -0
- skill_sentry_cli/server/demo_backend.py +475 -0
- skill_sentry_cli/server/demo_data.py +814 -0
- skill_sentry_cli/server/demo_mode.py +40 -0
- skill_sentry_cli/server/errors.py +48 -0
- skill_sentry_cli/server/live_backend.py +576 -0
- skill_sentry_cli/server/routes.py +191 -0
- skill_sentry_cli/skill/SKILL.md +67 -0
- skill_sentry_cli/skill/scripts/audit.sh +48 -0
- skill_sentry_cli/ui/dist/assets/index-Bf0h24IX.css +1 -0
- skill_sentry_cli/ui/dist/assets/index-CrYBKSSB.js +8 -0
- skill_sentry_cli/ui/dist/index.html +14 -0
- skill_sentry_cli-2.0.0.dist-info/METADATA +76 -0
- skill_sentry_cli-2.0.0.dist-info/RECORD +96 -0
- skill_sentry_cli-2.0.0.dist-info/WHEEL +4 -0
- skill_sentry_cli-2.0.0.dist-info/entry_points.txt +2 -0
- skill_sentry_cli-2.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
"""Vision scanning for raster payloads and SVG instruction injection."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from skill_sentry_cli.core.contracts import (
|
|
9
|
+
Finding,
|
|
10
|
+
FindingEvidence,
|
|
11
|
+
Severity,
|
|
12
|
+
Stage,
|
|
13
|
+
StageResult,
|
|
14
|
+
VisionEvidence,
|
|
15
|
+
utc_now,
|
|
16
|
+
)
|
|
17
|
+
from skill_sentry_cli.core.models.base import ModelClient
|
|
18
|
+
from skill_sentry_cli.core.parser import SkillPackage
|
|
19
|
+
from skill_sentry_cli.core.vision.image_extractor import ImageAsset, extract_images
|
|
20
|
+
|
|
21
|
+
MAX_OCR_CHARS = 20_000
|
|
22
|
+
# Raster images sent to the model per scan; the rest are reported as partial coverage.
|
|
23
|
+
MAX_MODEL_IMAGES = 3
|
|
24
|
+
_SEVERITY = {
|
|
25
|
+
"critical": Severity.CRITICAL,
|
|
26
|
+
"high": Severity.HIGH,
|
|
27
|
+
"medium": Severity.MEDIUM,
|
|
28
|
+
"low": Severity.LOW,
|
|
29
|
+
"note": Severity.NOTE,
|
|
30
|
+
}
|
|
31
|
+
_SEVERITY_SCORE = {
|
|
32
|
+
Severity.CRITICAL: 95,
|
|
33
|
+
Severity.HIGH: 72,
|
|
34
|
+
Severity.MEDIUM: 42,
|
|
35
|
+
Severity.LOW: 18,
|
|
36
|
+
Severity.NOTE: 0,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
_SVG_ACTIVE = re.compile(
|
|
40
|
+
r"<\s*(?:script|foreignObject|iframe|object|embed)\b|on[a-z]+\s*=|"
|
|
41
|
+
r"javascript\s*:|xlink:href\s*=\s*[\"'](?:https?:|data:text/html)|"
|
|
42
|
+
# Entity and DTD declarations are how XML entity-expansion attacks arrive.
|
|
43
|
+
r"<!\s*(?:ENTITY|DOCTYPE)\b|"
|
|
44
|
+
# A <use> that points outside the file pulls in remote or inline content.
|
|
45
|
+
r"<\s*use\b[^>]*\bhref\s*=\s*[\"'](?!#)|"
|
|
46
|
+
# Any data: URI can carry an active document, whatever its media type.
|
|
47
|
+
r"\bhref\s*=\s*[\"']\s*data:|"
|
|
48
|
+
# Remote stylesheets and url() fetches run during rendering.
|
|
49
|
+
r"<\s*style\b[^>]*>[^<]*(?:@import|url\(\s*[\"']?https?:)|"
|
|
50
|
+
# Text the reader cannot see is a common carrier for injected instructions.
|
|
51
|
+
r"<\s*text\b[^>]*(?:opacity\s*[:=]\s*[\"']?0(?:\.0*)?[\"';\s>]|"
|
|
52
|
+
r"font-size\s*[:=]\s*[\"']?0?\.\d+|fill\s*[:=]\s*[\"']?none)",
|
|
53
|
+
re.IGNORECASE,
|
|
54
|
+
)
|
|
55
|
+
_INSTRUCTION = re.compile(
|
|
56
|
+
r"(?:ignore|disregard)\s+(?:all\s+)?(?:previous|prior)\s+instructions?|"
|
|
57
|
+
r"(?:read|open|include|send|upload|exfiltrate)[^\n]{0,100}(?:~|/home/[^/\s]+)/\.ssh/|"
|
|
58
|
+
r"(?:send|post|upload)[^\n]{0,100}(?:token|secret|credential|api[_\s-]?key)",
|
|
59
|
+
re.IGNORECASE,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _normalise_bbox(raw: Any) -> list[float]:
|
|
64
|
+
if not isinstance(raw, list) or len(raw) != 4:
|
|
65
|
+
return []
|
|
66
|
+
values: list[float] = []
|
|
67
|
+
for item in raw:
|
|
68
|
+
if not isinstance(item, (int, float)) or isinstance(item, bool):
|
|
69
|
+
return []
|
|
70
|
+
values.append(max(0.0, min(1.0, float(item))))
|
|
71
|
+
return values
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _decode_svg(data: bytes) -> str:
|
|
75
|
+
"""Decode an SVG the way an XML reader would, so UTF-16 cannot hide markup."""
|
|
76
|
+
|
|
77
|
+
if data.startswith((b"\xff\xfe", b"\xfe\xff")):
|
|
78
|
+
return data.decode("utf-16", "replace")
|
|
79
|
+
if data.count(b"\x00") > len(data) // 4:
|
|
80
|
+
order = "utf-16-be" if data[:1] == b"\x00" else "utf-16-le"
|
|
81
|
+
return data.decode(order, "replace")
|
|
82
|
+
return data.decode("utf-8", "replace")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _svg_scan(asset: ImageAsset) -> tuple[list[Finding], str]:
|
|
86
|
+
# The whole document is scanned; only the stored excerpt is truncated.
|
|
87
|
+
full = _decode_svg(asset.data)
|
|
88
|
+
text = full[:MAX_OCR_CHARS]
|
|
89
|
+
findings: list[Finding] = []
|
|
90
|
+
active = _SVG_ACTIVE.search(full)
|
|
91
|
+
if active:
|
|
92
|
+
findings.append(
|
|
93
|
+
Finding(
|
|
94
|
+
stage=Stage.VISION,
|
|
95
|
+
severity=Severity.HIGH,
|
|
96
|
+
message="Active content detected in SVG asset",
|
|
97
|
+
type="svg-active-content",
|
|
98
|
+
evidence=FindingEvidence(
|
|
99
|
+
file=asset.relative,
|
|
100
|
+
line=1,
|
|
101
|
+
code=active.group(0)[:200],
|
|
102
|
+
),
|
|
103
|
+
)
|
|
104
|
+
)
|
|
105
|
+
if _INSTRUCTION.search(full):
|
|
106
|
+
match = _INSTRUCTION.search(full)
|
|
107
|
+
findings.append(
|
|
108
|
+
Finding(
|
|
109
|
+
stage=Stage.VISION,
|
|
110
|
+
severity=Severity.CRITICAL,
|
|
111
|
+
message="Instruction injection detected in SVG asset",
|
|
112
|
+
type="hidden-image-instruction",
|
|
113
|
+
evidence=FindingEvidence(
|
|
114
|
+
file=asset.relative,
|
|
115
|
+
line=1,
|
|
116
|
+
code=(match.group(0) if match else text[:200])[:500],
|
|
117
|
+
ocr=text[:MAX_OCR_CHARS],
|
|
118
|
+
),
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
return findings, text
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def scan_vision(
|
|
125
|
+
package: SkillPackage,
|
|
126
|
+
model: ModelClient | None,
|
|
127
|
+
*,
|
|
128
|
+
max_px: int = 1024,
|
|
129
|
+
) -> StageResult:
|
|
130
|
+
"""Scan bundled images. Missing images are not a degraded stage."""
|
|
131
|
+
|
|
132
|
+
assets, notes = extract_images(package)
|
|
133
|
+
if not assets:
|
|
134
|
+
return StageResult(
|
|
135
|
+
stage=Stage.VISION,
|
|
136
|
+
findings=[],
|
|
137
|
+
signals={},
|
|
138
|
+
telemetry={"images_scanned": 0, "notes": notes, "vision_status": "not_applicable"},
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
findings: list[Finding] = []
|
|
142
|
+
evidence: list[VisionEvidence] = []
|
|
143
|
+
degraded_errors: list[str] = []
|
|
144
|
+
analysed = 0
|
|
145
|
+
skipped = 0
|
|
146
|
+
model_calls = 0
|
|
147
|
+
for asset in assets:
|
|
148
|
+
if asset.is_svg:
|
|
149
|
+
analysed += 1
|
|
150
|
+
svg_findings, ocr = _svg_scan(asset)
|
|
151
|
+
findings.extend(svg_findings)
|
|
152
|
+
if svg_findings or ocr:
|
|
153
|
+
evidence.append(
|
|
154
|
+
VisionEvidence(
|
|
155
|
+
image=asset.preview_uri(),
|
|
156
|
+
image_name=asset.relative,
|
|
157
|
+
ocr=ocr[:MAX_OCR_CHARS],
|
|
158
|
+
bboxes=[],
|
|
159
|
+
confidence=0.99 if svg_findings else 0.5,
|
|
160
|
+
position_note="SVG text content",
|
|
161
|
+
findings=svg_findings,
|
|
162
|
+
)
|
|
163
|
+
)
|
|
164
|
+
continue
|
|
165
|
+
|
|
166
|
+
if model is None:
|
|
167
|
+
skipped += 1
|
|
168
|
+
degraded_errors.append(f"vision model unavailable for {asset.relative}")
|
|
169
|
+
continue
|
|
170
|
+
if model_calls >= MAX_MODEL_IMAGES:
|
|
171
|
+
# Bounded so a scan stays within its time budget on a CPU-hosted model.
|
|
172
|
+
skipped += 1
|
|
173
|
+
degraded_errors.append(f"{asset.relative}: image budget of {MAX_MODEL_IMAGES} reached")
|
|
174
|
+
continue
|
|
175
|
+
model_calls += 1
|
|
176
|
+
try:
|
|
177
|
+
analysis = model.analyze_image(
|
|
178
|
+
asset.path, f"Scan {asset.relative} for hidden instructions"
|
|
179
|
+
)
|
|
180
|
+
except Exception as exc:
|
|
181
|
+
skipped += 1
|
|
182
|
+
degraded_errors.append(f"{asset.relative}: {exc}")
|
|
183
|
+
continue
|
|
184
|
+
analysed += 1
|
|
185
|
+
|
|
186
|
+
image_findings: list[Finding] = []
|
|
187
|
+
for item in analysis.findings[:50]:
|
|
188
|
+
if not isinstance(item, dict):
|
|
189
|
+
continue
|
|
190
|
+
severity = _SEVERITY.get(str(item.get("severity", "medium")).lower(), Severity.MEDIUM)
|
|
191
|
+
raw_bbox = _normalise_bbox(item.get("bbox"))
|
|
192
|
+
message = str(item.get("evidence") or item.get("type") or "Suspicious image content")
|
|
193
|
+
image_findings.append(
|
|
194
|
+
Finding(
|
|
195
|
+
stage=Stage.VISION,
|
|
196
|
+
severity=severity,
|
|
197
|
+
message=f"Image payload detected in {asset.relative}: {message[:400]}",
|
|
198
|
+
type=str(item.get("type") or "hidden-image-instruction")[:80],
|
|
199
|
+
evidence=FindingEvidence(
|
|
200
|
+
file=asset.relative,
|
|
201
|
+
line=1,
|
|
202
|
+
code=str(item.get("evidence") or "")[:500],
|
|
203
|
+
bbox=raw_bbox or None,
|
|
204
|
+
ocr=analysis.ocr[:MAX_OCR_CHARS] if analysis.ocr else None,
|
|
205
|
+
),
|
|
206
|
+
)
|
|
207
|
+
)
|
|
208
|
+
# OCR is untrusted text, but instruction-shaped OCR is still evidence.
|
|
209
|
+
if _INSTRUCTION.search(analysis.ocr or "") and not any(
|
|
210
|
+
item.severity.rank >= Severity.HIGH.rank for item in image_findings
|
|
211
|
+
):
|
|
212
|
+
image_findings.append(
|
|
213
|
+
Finding(
|
|
214
|
+
stage=Stage.VISION,
|
|
215
|
+
severity=Severity.CRITICAL,
|
|
216
|
+
message=f"Hidden instruction in image OCR: {asset.relative}",
|
|
217
|
+
type="hidden-image-instruction",
|
|
218
|
+
evidence=FindingEvidence(
|
|
219
|
+
file=asset.relative,
|
|
220
|
+
line=1,
|
|
221
|
+
ocr=analysis.ocr[:MAX_OCR_CHARS],
|
|
222
|
+
),
|
|
223
|
+
)
|
|
224
|
+
)
|
|
225
|
+
findings.extend(image_findings)
|
|
226
|
+
evidence.append(
|
|
227
|
+
VisionEvidence(
|
|
228
|
+
image=asset.preview_uri(),
|
|
229
|
+
image_name=asset.relative,
|
|
230
|
+
ocr=(analysis.ocr or "")[:MAX_OCR_CHARS],
|
|
231
|
+
bboxes=[b for b in (_normalise_bbox(item) for item in analysis.bboxes) if b],
|
|
232
|
+
confidence=0.95 if image_findings else 0.5,
|
|
233
|
+
position_note="hidden image payload" if image_findings else None,
|
|
234
|
+
findings=image_findings,
|
|
235
|
+
)
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
score = 0.0
|
|
239
|
+
if findings:
|
|
240
|
+
score = max(_SEVERITY_SCORE[item.severity] for item in findings)
|
|
241
|
+
score = min(100.0, score + min(8, (len(findings) - 1) * 2))
|
|
242
|
+
if skipped == 0:
|
|
243
|
+
vision_status = "evaluated"
|
|
244
|
+
elif analysed == 0:
|
|
245
|
+
# Deferred or unavailable vision must never read as a clean image result.
|
|
246
|
+
vision_status = "not_evaluated"
|
|
247
|
+
else:
|
|
248
|
+
vision_status = "partial"
|
|
249
|
+
telemetry = {
|
|
250
|
+
"images_scanned": len(evidence),
|
|
251
|
+
"images_total": len(assets),
|
|
252
|
+
"vision_status": vision_status,
|
|
253
|
+
"notes": notes,
|
|
254
|
+
"evidence": [item.model_dump(by_alias=True, mode="json") for item in evidence],
|
|
255
|
+
"scanned_at": utc_now().isoformat(),
|
|
256
|
+
}
|
|
257
|
+
if degraded_errors:
|
|
258
|
+
return StageResult(
|
|
259
|
+
stage=Stage.VISION,
|
|
260
|
+
findings=findings,
|
|
261
|
+
signals={"vision": score} if findings else {},
|
|
262
|
+
telemetry=telemetry,
|
|
263
|
+
degraded=True,
|
|
264
|
+
error="; ".join(degraded_errors)[:1000],
|
|
265
|
+
)
|
|
266
|
+
return StageResult(
|
|
267
|
+
stage=Stage.VISION,
|
|
268
|
+
findings=findings,
|
|
269
|
+
signals={"vision": score},
|
|
270
|
+
telemetry=telemetry,
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
__all__ = ["scan_vision"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Packaged data for SkillSentry (loaded with importlib.resources)."""
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "2.0.0",
|
|
3
|
+
"description": "Curated dictionary of known legitimate AI Agent Skill names for typosquat detection. Union of the v1 core list and the Developer 2 curation.",
|
|
4
|
+
"updated": "2026-10-30",
|
|
5
|
+
"source": "Union of skillsentry core names and feature/developer-2 skillsentry/data/known_skills_v1.json (chauhanaman41).",
|
|
6
|
+
"entries": [
|
|
7
|
+
{
|
|
8
|
+
"name": "api-tester",
|
|
9
|
+
"category": "web-api"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"name": "aws-deploy",
|
|
13
|
+
"category": "cloud"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"name": "aws-lambda",
|
|
17
|
+
"category": "cloud"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"name": "aws-sync",
|
|
21
|
+
"category": "cloud"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"name": "azure-deploy",
|
|
25
|
+
"category": "cloud"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"name": "backup-tool",
|
|
29
|
+
"category": "file-system"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"name": "calculator",
|
|
33
|
+
"category": "utility"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"name": "calendar-assistant",
|
|
37
|
+
"category": "core"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"name": "cert-checker",
|
|
41
|
+
"category": "security"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"name": "changelog-gen",
|
|
45
|
+
"category": "development"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"name": "cloud-backup",
|
|
49
|
+
"category": "cloud"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"name": "cloud-deploy",
|
|
53
|
+
"category": "cloud"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"name": "cloud-sync",
|
|
57
|
+
"category": "cloud"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"name": "code-formatter",
|
|
61
|
+
"category": "development"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"name": "code-reviewer",
|
|
65
|
+
"category": "core"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"name": "csv-analyzer",
|
|
69
|
+
"category": "data-ai"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"name": "data-pipeline",
|
|
73
|
+
"category": "data-ai"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"name": "database-helper",
|
|
77
|
+
"category": "core"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"name": "dependency-checker",
|
|
81
|
+
"category": "development"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"name": "discord-bot",
|
|
85
|
+
"category": "communication"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"name": "docker-build",
|
|
89
|
+
"category": "container"
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"name": "docker-compose",
|
|
93
|
+
"category": "container"
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"name": "docker-helper",
|
|
97
|
+
"category": "core"
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"name": "email-assistant",
|
|
101
|
+
"category": "core"
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"name": "email-sender",
|
|
105
|
+
"category": "communication"
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"name": "file-manager",
|
|
109
|
+
"category": "file-system"
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"name": "file-organizer",
|
|
113
|
+
"category": "core"
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"name": "git-commit",
|
|
117
|
+
"category": "git-devops"
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"name": "git-helper",
|
|
121
|
+
"category": "git-devops"
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"name": "git-review",
|
|
125
|
+
"category": "git-devops"
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"name": "git-translate",
|
|
129
|
+
"category": "git-devops"
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"name": "github-actions",
|
|
133
|
+
"category": "git-devops"
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"name": "github-helper",
|
|
137
|
+
"category": "core"
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"name": "github-pr",
|
|
141
|
+
"category": "git-devops"
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"name": "gitlab-ci",
|
|
145
|
+
"category": "git-devops"
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
"name": "helm-chart",
|
|
149
|
+
"category": "container"
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
"name": "http-client",
|
|
153
|
+
"category": "web-api"
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
"name": "image-resizer",
|
|
157
|
+
"category": "file-system"
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
"name": "json-formatter",
|
|
161
|
+
"category": "data-ai"
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"name": "k8s-deploy",
|
|
165
|
+
"category": "container"
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
"name": "kubernetes-tools",
|
|
169
|
+
"category": "container"
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
"name": "lint-runner",
|
|
173
|
+
"category": "development"
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
"name": "markdown-editor",
|
|
177
|
+
"category": "core"
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
"name": "ml-trainer",
|
|
181
|
+
"category": "data-ai"
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
"name": "notion-sync",
|
|
185
|
+
"category": "core"
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
"name": "password-gen",
|
|
189
|
+
"category": "security"
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
"name": "pdf-reader",
|
|
193
|
+
"category": "core"
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
"name": "pdf-summarizer",
|
|
197
|
+
"category": "data-ai"
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
"name": "secret-rotator",
|
|
201
|
+
"category": "security"
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
"name": "slack-bot",
|
|
205
|
+
"category": "communication"
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
"name": "slack-message",
|
|
209
|
+
"category": "communication"
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
"name": "slack-notifier",
|
|
213
|
+
"category": "core"
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
"name": "slack-notify",
|
|
217
|
+
"category": "communication"
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
"name": "ssh-manager",
|
|
221
|
+
"category": "security"
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
"name": "teams-notify",
|
|
225
|
+
"category": "communication"
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
"name": "test-runner",
|
|
229
|
+
"category": "development"
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
"name": "time-tracker",
|
|
233
|
+
"category": "utility"
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
"name": "translator",
|
|
237
|
+
"category": "utility"
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
"name": "url-shortener",
|
|
241
|
+
"category": "web-api"
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
"name": "video-converter",
|
|
245
|
+
"category": "file-system"
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
"name": "vuln-scanner",
|
|
249
|
+
"category": "security"
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
"name": "weather-bot",
|
|
253
|
+
"category": "utility"
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
"name": "weather-fetch",
|
|
257
|
+
"category": "utility"
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
"name": "web-scraper",
|
|
261
|
+
"category": "web-api"
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
"name": "web-search",
|
|
265
|
+
"category": "core"
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
"name": "webhook-handler",
|
|
269
|
+
"category": "web-api"
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
"name": "zip-extractor",
|
|
273
|
+
"category": "file-system"
|
|
274
|
+
}
|
|
275
|
+
]
|
|
276
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
version = 2
|
|
2
|
+
|
|
3
|
+
[sources]
|
|
4
|
+
env_name_patterns = [
|
|
5
|
+
"(?i)(token|secret|password|passwd|api[_-]?key|access[_-]?key|credential|private[_-]?key)"
|
|
6
|
+
]
|
|
7
|
+
path_globs = [
|
|
8
|
+
"~/.aws/*",
|
|
9
|
+
"~/.ssh/*",
|
|
10
|
+
"~/.config/gcloud/*",
|
|
11
|
+
"~/.netrc",
|
|
12
|
+
"~/.docker/config.json",
|
|
13
|
+
"~/.npmrc",
|
|
14
|
+
"~/.pypirc",
|
|
15
|
+
"~/.kube/config",
|
|
16
|
+
"**/.env",
|
|
17
|
+
"~/.git-credentials"
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[sinks]
|
|
21
|
+
python_calls = [
|
|
22
|
+
"requests.post",
|
|
23
|
+
"requests.put",
|
|
24
|
+
"requests.patch",
|
|
25
|
+
"requests.get",
|
|
26
|
+
"requests.request",
|
|
27
|
+
"httpx.post",
|
|
28
|
+
"httpx.put",
|
|
29
|
+
"httpx.get",
|
|
30
|
+
"httpx.request",
|
|
31
|
+
"urllib.request.urlopen",
|
|
32
|
+
"urllib.request.Request",
|
|
33
|
+
"http.client.HTTPSConnection",
|
|
34
|
+
"http.client.HTTPConnection",
|
|
35
|
+
"socket.gethostbyname",
|
|
36
|
+
"socket.getaddrinfo",
|
|
37
|
+
"socket.create_connection"
|
|
38
|
+
]
|
|
39
|
+
js_patterns = [
|
|
40
|
+
"fetch\\(",
|
|
41
|
+
"axios\\.",
|
|
42
|
+
"XMLHttpRequest",
|
|
43
|
+
"https?\\.request\\("
|
|
44
|
+
]
|
|
45
|
+
shell_patterns = [
|
|
46
|
+
"curl\\s",
|
|
47
|
+
"wget\\s",
|
|
48
|
+
"nc\\s",
|
|
49
|
+
"dig\\s",
|
|
50
|
+
"nslookup\\s"
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# Service bindings (feature/developer-2): a credential belongs to the vendor behind its hosts.
|
|
55
|
+
[[service_bindings]]
|
|
56
|
+
name = "github"
|
|
57
|
+
credential_env = ["(?i)^(GITHUB|GH)_(TOKEN|PAT)$"]
|
|
58
|
+
hosts = ["github.com", "githubusercontent.com"]
|
|
59
|
+
|
|
60
|
+
[[service_bindings]]
|
|
61
|
+
name = "slack"
|
|
62
|
+
credential_env = ["(?i)^SLACK_.*(TOKEN|WEBHOOK|SECRET|URL)$"]
|
|
63
|
+
hosts = ["slack.com"]
|
|
64
|
+
|
|
65
|
+
[[service_bindings]]
|
|
66
|
+
name = "aws"
|
|
67
|
+
credential_env = ["(?i)^AWS_.*"]
|
|
68
|
+
credential_paths = ["~/.aws/*"]
|
|
69
|
+
hosts = ["amazonaws.com"]
|
|
70
|
+
|
|
71
|
+
[[service_bindings]]
|
|
72
|
+
name = "openai"
|
|
73
|
+
credential_env = ["(?i)^OPENAI_API_KEY$"]
|
|
74
|
+
hosts = ["openai.com"]
|
|
75
|
+
|
|
76
|
+
[[service_bindings]]
|
|
77
|
+
name = "anthropic"
|
|
78
|
+
credential_env = ["(?i)^ANTHROPIC_API_KEY$"]
|
|
79
|
+
hosts = ["anthropic.com"]
|
|
80
|
+
|
|
81
|
+
[[service_bindings]]
|
|
82
|
+
name = "gitlab"
|
|
83
|
+
credential_env = ["(?i)^GITLAB_.*(TOKEN|PAT)$"]
|
|
84
|
+
hosts = ["gitlab.com"]
|
|
85
|
+
|
|
86
|
+
[[service_bindings]]
|
|
87
|
+
name = "pypi"
|
|
88
|
+
credential_env = ["(?i)^(PYPI_TOKEN|TWINE_PASSWORD)$"]
|
|
89
|
+
hosts = ["pypi.org"]
|
|
90
|
+
|
|
91
|
+
[[service_bindings]]
|
|
92
|
+
name = "npm"
|
|
93
|
+
credential_env = ["(?i)^NPM_TOKEN$"]
|
|
94
|
+
hosts = ["npmjs.org", "npmjs.com"]
|