aiden-runtime 3.19.4 → 3.19.6
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.
- package/config/devos.config.backup.json +224 -224
- package/config/devos.config.json +1 -1
- package/dist/api/server.js +7 -1
- package/dist/core/agentLoop.js +8 -3
- package/dist/core/skillLoader.js +2 -0
- package/dist/core/slashAsTool.js +2 -0
- package/dist/core/toolRegistry.js +16 -0
- package/dist/core/version.js +1 -1
- package/dist/providers/router.js +46 -6
- package/dist-bundle/cli.js +911 -557
- package/dist-bundle/index.js +758 -658
- package/package.json +268 -266
- package/scripts/postinstall.js +58 -1
- package/workspace-templates/HEARTBEAT.md +16 -0
- package/workspace-templates/SOUL.md +267 -0
- package/workspace-templates/STANDING_ORDERS.md +21 -0
- package/workspace-templates/permissions.yaml +180 -0
- package/workspace-templates/skills/architecture-diagram/SKILL.md +126 -0
- package/workspace-templates/skills/architecture-diagram/skill.json +25 -0
- package/workspace-templates/skills/arxiv/SKILL.md +124 -0
- package/workspace-templates/skills/arxiv/skill.json +26 -0
- package/workspace-templates/skills/ascii-art/SKILL.md +142 -0
- package/workspace-templates/skills/ascii-art/skill.json +26 -0
- package/workspace-templates/skills/blogwatcher/SKILL.md +147 -0
- package/workspace-templates/skills/blogwatcher/skill.json +26 -0
- package/workspace-templates/skills/censys/SKILL.md +104 -0
- package/workspace-templates/skills/censys/index.ts +133 -0
- package/workspace-templates/skills/censys/skill.json +25 -0
- package/workspace-templates/skills/clipboard-history/SKILL.md +101 -0
- package/workspace-templates/skills/clipboard-history/skill.json +23 -0
- package/workspace-templates/skills/crt-sh/SKILL.md +102 -0
- package/workspace-templates/skills/crt-sh/index.ts +59 -0
- package/workspace-templates/skills/crt-sh/skill.json +25 -0
- package/workspace-templates/skills/cveapi/SKILL.md +114 -0
- package/workspace-templates/skills/cveapi/index.ts +249 -0
- package/workspace-templates/skills/cveapi/skill.json +25 -0
- package/workspace-templates/skills/docker-management/SKILL.md +156 -0
- package/workspace-templates/skills/docker-management/skill.json +25 -0
- package/workspace-templates/skills/excalidraw/SKILL.md +148 -0
- package/workspace-templates/skills/excalidraw/skill.json +25 -0
- package/workspace-templates/skills/explainshell/SKILL.md +93 -0
- package/workspace-templates/skills/explainshell/index.ts +132 -0
- package/workspace-templates/skills/explainshell/skill.json +25 -0
- package/workspace-templates/skills/financial_research/SKILL.md +21 -0
- package/workspace-templates/skills/financial_research/skill.json +24 -0
- package/workspace-templates/skills/gif-search/SKILL.md +122 -0
- package/workspace-templates/skills/gif-search/skill.json +25 -0
- package/workspace-templates/skills/github-auth/SKILL.md +134 -0
- package/workspace-templates/skills/github-auth/skill.json +26 -0
- package/workspace-templates/skills/github-issues/SKILL.md +130 -0
- package/workspace-templates/skills/github-issues/skill.json +25 -0
- package/workspace-templates/skills/github-pr-workflow/SKILL.md +143 -0
- package/workspace-templates/skills/github-pr-workflow/skill.json +26 -0
- package/workspace-templates/skills/github-repo-management/SKILL.md +147 -0
- package/workspace-templates/skills/github-repo-management/skill.json +26 -0
- package/workspace-templates/skills/google-workspace/SKILL.md +110 -0
- package/workspace-templates/skills/google-workspace/skill.json +26 -0
- package/workspace-templates/skills/greynoise/SKILL.md +96 -0
- package/workspace-templates/skills/greynoise/index.ts +107 -0
- package/workspace-templates/skills/greynoise/skill.json +25 -0
- package/workspace-templates/skills/haveibeenpwned/SKILL.md +100 -0
- package/workspace-templates/skills/haveibeenpwned/index.ts +72 -0
- package/workspace-templates/skills/haveibeenpwned/skill.json +24 -0
- package/workspace-templates/skills/jupyter-live-kernel/SKILL.md +116 -0
- package/workspace-templates/skills/jupyter-live-kernel/skill.json +25 -0
- package/workspace-templates/skills/linear/SKILL.md +107 -0
- package/workspace-templates/skills/linear/skill.json +25 -0
- package/workspace-templates/skills/nano-pdf/SKILL.md +113 -0
- package/workspace-templates/skills/nano-pdf/skill.json +26 -0
- package/workspace-templates/skills/notion/SKILL.md +108 -0
- package/workspace-templates/skills/notion/skill.json +24 -0
- package/workspace-templates/skills/obsidian/SKILL.md +115 -0
- package/workspace-templates/skills/obsidian/skill.json +24 -0
- package/workspace-templates/skills/ocr-and-documents/SKILL.md +125 -0
- package/workspace-templates/skills/ocr-and-documents/skill.json +26 -0
- package/workspace-templates/skills/p5js/SKILL.md +163 -0
- package/workspace-templates/skills/p5js/skill.json +24 -0
- package/workspace-templates/skills/research-paper-writing/SKILL.md +158 -0
- package/workspace-templates/skills/research-paper-writing/skill.json +26 -0
- package/workspace-templates/skills/securityheaders/SKILL.md +99 -0
- package/workspace-templates/skills/securityheaders/index.ts +213 -0
- package/workspace-templates/skills/securityheaders/skill.json +26 -0
- package/workspace-templates/skills/shodan/SKILL.md +113 -0
- package/workspace-templates/skills/shodan/index.ts +94 -0
- package/workspace-templates/skills/shodan/skill.json +26 -0
- package/workspace-templates/skills/songsee/SKILL.md +152 -0
- package/workspace-templates/skills/songsee/skill.json +25 -0
- package/workspace-templates/skills/ssllabs/SKILL.md +107 -0
- package/workspace-templates/skills/ssllabs/index.ts +208 -0
- package/workspace-templates/skills/ssllabs/skill.json +27 -0
- package/workspace-templates/skills/stable-diffusion-image-generation/SKILL.md +136 -0
- package/workspace-templates/skills/stable-diffusion-image-generation/skill.json +24 -0
- package/workspace-templates/skills/systematic-debugging/SKILL.md +131 -0
- package/workspace-templates/skills/systematic-debugging/skill.json +25 -0
- package/workspace-templates/skills/test-driven-development/SKILL.md +164 -0
- package/workspace-templates/skills/test-driven-development/skill.json +25 -0
- package/workspace-templates/skills/urlscan/SKILL.md +118 -0
- package/workspace-templates/skills/urlscan/index.ts +94 -0
- package/workspace-templates/skills/urlscan/skill.json +24 -0
- package/workspace-templates/skills/virustotal/SKILL.md +120 -0
- package/workspace-templates/skills/virustotal/index.ts +124 -0
- package/workspace-templates/skills/virustotal/skill.json +26 -0
- package/workspace-templates/skills/web_research/SKILL.md +18 -0
- package/workspace-templates/skills/web_research/skill.json +20 -0
- package/workspace-templates/skills/xitter/SKILL.md +148 -0
- package/workspace-templates/skills/xitter/skill.json +26 -0
- package/workspace-templates/skills/youtube-content/SKILL.md +121 -0
- package/workspace-templates/skills/youtube-content/skill.json +25 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: test-driven-development
|
|
3
|
+
description: Write software using the RED-GREEN-REFACTOR cycle for reliable, well-tested code
|
|
4
|
+
category: developer
|
|
5
|
+
version: 1.0.0
|
|
6
|
+
origin: aiden
|
|
7
|
+
license: Apache-2.0
|
|
8
|
+
tags: tdd, testing, red-green-refactor, unit-tests, pytest, jest, vitest, quality, development
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Test-Driven Development (TDD)
|
|
12
|
+
|
|
13
|
+
Use the RED-GREEN-REFACTOR cycle to write reliable, well-tested software. Write tests first, then write the minimal code to pass them, then clean up.
|
|
14
|
+
|
|
15
|
+
## When to Use
|
|
16
|
+
|
|
17
|
+
- User wants to implement a new function or module correctly
|
|
18
|
+
- User wants to ensure edge cases are handled before they become bugs
|
|
19
|
+
- User is refactoring and wants a safety net
|
|
20
|
+
- User wants tests for business-critical logic
|
|
21
|
+
- User wants to document behavior through executable tests
|
|
22
|
+
|
|
23
|
+
## How to Use
|
|
24
|
+
|
|
25
|
+
### The TDD cycle
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
RED → Write a failing test for the behavior you want
|
|
29
|
+
GREEN → Write the minimum code to make the test pass
|
|
30
|
+
REFACTOR → Clean up the code without breaking tests
|
|
31
|
+
Repeat for each small behavior increment
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Phase 1: RED — Write a failing test first
|
|
35
|
+
|
|
36
|
+
Write the test before the implementation. It must fail to prove the test works.
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
# test_calculator.py — write this BEFORE calculator.py
|
|
40
|
+
import pytest
|
|
41
|
+
from calculator import add
|
|
42
|
+
|
|
43
|
+
def test_add_two_positive_numbers():
|
|
44
|
+
assert add(2, 3) == 5
|
|
45
|
+
|
|
46
|
+
def test_add_negative_number():
|
|
47
|
+
assert add(-1, 5) == 4
|
|
48
|
+
|
|
49
|
+
def test_add_floats():
|
|
50
|
+
assert abs(add(0.1, 0.2) - 0.3) < 1e-9
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Run the tests — they should fail with `ModuleNotFoundError` or `ImportError`:
|
|
54
|
+
|
|
55
|
+
```powershell
|
|
56
|
+
pytest test_calculator.py -v
|
|
57
|
+
# Expected: FAILED (module not found)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Phase 2: GREEN — Minimal implementation to pass
|
|
61
|
+
|
|
62
|
+
Write only enough code to make the tests pass — nothing more.
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
# calculator.py
|
|
66
|
+
def add(a, b):
|
|
67
|
+
return a + b
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
```powershell
|
|
71
|
+
pytest test_calculator.py -v
|
|
72
|
+
# Expected: 3 PASSED
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Phase 3: REFACTOR — Improve code quality
|
|
76
|
+
|
|
77
|
+
Clean up implementation and tests while keeping all tests green.
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
# calculator.py — add type hints and docstring
|
|
81
|
+
def add(a: float, b: float) -> float:
|
|
82
|
+
"""Return the sum of a and b."""
|
|
83
|
+
return a + b
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
```powershell
|
|
87
|
+
pytest test_calculator.py -v # must still pass after refactor
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Write tests with pytest (Python)
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
# Parametrize for multiple cases
|
|
94
|
+
@pytest.mark.parametrize("a,b,expected", [
|
|
95
|
+
(2, 3, 5),
|
|
96
|
+
(-1, 5, 4),
|
|
97
|
+
(0, 0, 0),
|
|
98
|
+
(1.5, 2.5, 4.0),
|
|
99
|
+
])
|
|
100
|
+
def test_add(a, b, expected):
|
|
101
|
+
assert add(a, b) == expected
|
|
102
|
+
|
|
103
|
+
# Test exceptions
|
|
104
|
+
def test_add_rejects_string():
|
|
105
|
+
with pytest.raises(TypeError):
|
|
106
|
+
add("hello", 2)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Write tests with Vitest / Jest (TypeScript / JavaScript)
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
// calculator.test.ts
|
|
113
|
+
import { describe, it, expect } from 'vitest'
|
|
114
|
+
import { add } from './calculator'
|
|
115
|
+
|
|
116
|
+
describe('add()', () => {
|
|
117
|
+
it('adds two positive numbers', () => {
|
|
118
|
+
expect(add(2, 3)).toBe(5)
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('handles negative numbers', () => {
|
|
122
|
+
expect(add(-1, 5)).toBe(4)
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('throws on non-number input', () => {
|
|
126
|
+
expect(() => add('a' as any, 2)).toThrow()
|
|
127
|
+
})
|
|
128
|
+
})
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### TDD for API endpoints
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
# test_api.py — RED first
|
|
135
|
+
def test_create_user_returns_201(client):
|
|
136
|
+
resp = client.post("/users", json={"name": "Alice", "email": "alice@example.com"})
|
|
137
|
+
assert resp.status_code == 201
|
|
138
|
+
assert resp.json()["id"] is not None
|
|
139
|
+
|
|
140
|
+
def test_create_user_rejects_missing_email(client):
|
|
141
|
+
resp = client.post("/users", json={"name": "Alice"})
|
|
142
|
+
assert resp.status_code == 422
|
|
143
|
+
|
|
144
|
+
# Then implement the /users endpoint to make these pass
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Examples
|
|
148
|
+
|
|
149
|
+
**"Implement a function that validates Indian phone numbers"**
|
|
150
|
+
→ RED: write tests for +91 10-digit numbers, reject 9-digit, reject letters. GREEN: implement regex. REFACTOR: add named groups, docstring.
|
|
151
|
+
|
|
152
|
+
**"Add a discount calculator to the checkout module"**
|
|
153
|
+
→ RED: test 10% off, 0% off, 100% off, reject negative discount. GREEN: minimal implementation. REFACTOR: extract constants, add type hints.
|
|
154
|
+
|
|
155
|
+
**"I want to refactor this function but keep it working"**
|
|
156
|
+
→ Write characterization tests first (tests that document current behavior), then refactor with test safety net.
|
|
157
|
+
|
|
158
|
+
## Cautions
|
|
159
|
+
|
|
160
|
+
- Never skip the RED phase — a test that was never failing might not actually test anything
|
|
161
|
+
- Tests should be independent — each test must set up its own state and not depend on test order
|
|
162
|
+
- Aim for one assertion per test — tests with multiple assertions are harder to diagnose when they fail
|
|
163
|
+
- TDD is slower upfront but faster overall — set this expectation with the user
|
|
164
|
+
- Mock external services (APIs, databases) in unit tests — test real integrations separately
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "test-driven-development",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Write software using the RED-GREEN-REFACTOR cycle for reliable, well-tested code",
|
|
5
|
+
"author": "aiden",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"tools": [],
|
|
8
|
+
"trigger_phrases": [],
|
|
9
|
+
"compatible_agents": [
|
|
10
|
+
"aiden"
|
|
11
|
+
],
|
|
12
|
+
"min_agent_version": "3.0.0",
|
|
13
|
+
"tags": [
|
|
14
|
+
"tdd",
|
|
15
|
+
"testing",
|
|
16
|
+
"red-green-refactor",
|
|
17
|
+
"unit-tests",
|
|
18
|
+
"pytest",
|
|
19
|
+
"jest",
|
|
20
|
+
"vitest",
|
|
21
|
+
"quality",
|
|
22
|
+
"development"
|
|
23
|
+
],
|
|
24
|
+
"created": "2026-04-27T17:11:40.912Z"
|
|
25
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: urlscan
|
|
3
|
+
description: Submit URLs for automated malware and phishing analysis, then retrieve safety verdicts and screenshots via urlscan.io
|
|
4
|
+
category: security
|
|
5
|
+
version: 1.0.0
|
|
6
|
+
license: Apache-2.0
|
|
7
|
+
origin: aiden
|
|
8
|
+
tags: security, malware, phishing, url, scan, osint, urlscan, threat-intel
|
|
9
|
+
env_required:
|
|
10
|
+
- URLSCAN_API_KEY
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# URL Safety Scanner (urlscan.io)
|
|
14
|
+
|
|
15
|
+
Submit any URL for analysis and receive a safety verdict, screenshot, and DNS/HTTP metadata. Uses the [urlscan.io](https://urlscan.io) API.
|
|
16
|
+
|
|
17
|
+
**Requires:** Free `URLSCAN_API_KEY` — sign up at urlscan.io, no credit card needed.
|
|
18
|
+
|
|
19
|
+
## When to Use
|
|
20
|
+
|
|
21
|
+
- User receives a suspicious link and wants to check it safely
|
|
22
|
+
- User wants to verify if a URL is phishing or malware
|
|
23
|
+
- User asks "is this link safe?" or "scan this URL for threats"
|
|
24
|
+
- Security review of links before sharing them
|
|
25
|
+
- OSINT: see what a URL looks like without visiting it yourself
|
|
26
|
+
|
|
27
|
+
## How to Use
|
|
28
|
+
|
|
29
|
+
### Submit a URL for scanning
|
|
30
|
+
|
|
31
|
+
```powershell
|
|
32
|
+
$url = "https://suspicious-site.example.com"
|
|
33
|
+
$key = $env:URLSCAN_API_KEY
|
|
34
|
+
$body = '{"url": "' + $url + '", "visibility": "unlisted"}'
|
|
35
|
+
|
|
36
|
+
$submit = Invoke-RestMethod -Uri "https://urlscan.io/api/v1/scan/" `
|
|
37
|
+
-Method POST `
|
|
38
|
+
-Headers @{ "API-Key" = $key; "Content-Type" = "application/json" } `
|
|
39
|
+
-Body $body
|
|
40
|
+
|
|
41
|
+
Write-Host "Scan submitted!"
|
|
42
|
+
Write-Host "UUID: $($submit.uuid)"
|
|
43
|
+
Write-Host "Results: $($submit.result)"
|
|
44
|
+
Write-Host "Waiting 30 seconds for scan to complete..."
|
|
45
|
+
Start-Sleep -Seconds 30
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Retrieve scan results after waiting
|
|
49
|
+
|
|
50
|
+
```powershell
|
|
51
|
+
$uuid = "PASTE-UUID-FROM-SUBMIT-STEP"
|
|
52
|
+
$key = $env:URLSCAN_API_KEY
|
|
53
|
+
|
|
54
|
+
$result = Invoke-RestMethod -Uri "https://urlscan.io/api/v1/result/$uuid/" `
|
|
55
|
+
-Headers @{ "API-Key" = $key }
|
|
56
|
+
|
|
57
|
+
Write-Host "Final URL: $($result.page.url)"
|
|
58
|
+
Write-Host "IP: $($result.page.ip)"
|
|
59
|
+
Write-Host "Country: $($result.page.country)"
|
|
60
|
+
Write-Host "Malicious: $($result.verdicts.overall.malicious)"
|
|
61
|
+
Write-Host "Phishing: $($result.verdicts.overall.phishing)"
|
|
62
|
+
Write-Host "Score: $($result.verdicts.overall.score)"
|
|
63
|
+
Write-Host "Screenshot: https://urlscan.io/screenshots/$uuid.png"
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### One-shot: submit and wait for result
|
|
67
|
+
|
|
68
|
+
```powershell
|
|
69
|
+
function Scan-Url {
|
|
70
|
+
param([string]$TargetUrl, [string]$ApiKey = $env:URLSCAN_API_KEY)
|
|
71
|
+
|
|
72
|
+
$body = '{"url": "' + $TargetUrl + '", "visibility": "unlisted"}'
|
|
73
|
+
$submit = Invoke-RestMethod -Uri "https://urlscan.io/api/v1/scan/" `
|
|
74
|
+
-Method POST -Headers @{ "API-Key" = $ApiKey; "Content-Type" = "application/json" } `
|
|
75
|
+
-Body $body
|
|
76
|
+
$uuid = $submit.uuid
|
|
77
|
+
|
|
78
|
+
Write-Host "Scanning $TargetUrl (uuid: $uuid)..."
|
|
79
|
+
Start-Sleep -Seconds 30
|
|
80
|
+
|
|
81
|
+
$result = Invoke-RestMethod -Uri "https://urlscan.io/api/v1/result/$uuid/" `
|
|
82
|
+
-Headers @{ "API-Key" = $ApiKey }
|
|
83
|
+
|
|
84
|
+
[PSCustomObject]@{
|
|
85
|
+
URL = $result.page.url
|
|
86
|
+
IP = $result.page.ip
|
|
87
|
+
Malicious = $result.verdicts.overall.malicious
|
|
88
|
+
Phishing = $result.verdicts.overall.phishing
|
|
89
|
+
Score = $result.verdicts.overall.score
|
|
90
|
+
Report = "https://urlscan.io/result/$uuid/"
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
Scan-Url -TargetUrl "https://example.com"
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Examples
|
|
98
|
+
|
|
99
|
+
**"Check if this link is safe: https://bit.ly/abc123"**
|
|
100
|
+
→ Submit with visibility "unlisted", wait 30 seconds, retrieve result.
|
|
101
|
+
|
|
102
|
+
**"Scan this suspicious email link for phishing"**
|
|
103
|
+
→ Use the one-shot function above. If `Phishing = True`, warn the user.
|
|
104
|
+
|
|
105
|
+
**"I want to see what this URL looks like without clicking it"**
|
|
106
|
+
→ Submit the scan, then use the screenshot URL: `https://urlscan.io/screenshots/{uuid}.png`
|
|
107
|
+
|
|
108
|
+
## Cautions
|
|
109
|
+
|
|
110
|
+
- Scans take 20–60 seconds to complete — always wait before fetching results
|
|
111
|
+
- `"visibility": "public"` makes the scan visible to all urlscan.io users — use `"unlisted"` for sensitive URLs
|
|
112
|
+
- Do not scan URLs that contain personal data or credentials in query parameters
|
|
113
|
+
- Free tier allows ~5 scans per minute — add delays for bulk scanning
|
|
114
|
+
- The API key is required to submit scans; public search results are accessible without a key
|
|
115
|
+
|
|
116
|
+
## Requirements
|
|
117
|
+
|
|
118
|
+
- `URLSCAN_API_KEY` — free account at https://urlscan.io (no credit card required)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// skills/urlscan/index.ts
|
|
2
|
+
// Programmatic handler — submits URLs and retrieves verdicts via urlscan.io API.
|
|
3
|
+
|
|
4
|
+
import { ApiSkill, requireApiKey } from '../../core/apiSkillBase'
|
|
5
|
+
|
|
6
|
+
const skill = new ApiSkill({
|
|
7
|
+
name: 'urlscan',
|
|
8
|
+
baseUrl: 'https://urlscan.io/api/v1',
|
|
9
|
+
apiKeyEnv: 'URLSCAN_API_KEY',
|
|
10
|
+
authType: 'header',
|
|
11
|
+
authHeader: 'API-Key',
|
|
12
|
+
rateLimit: { requests: 5, windowMs: 60_000 },
|
|
13
|
+
timeout: 20_000,
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
export type Visibility = 'public' | 'unlisted' | 'private'
|
|
17
|
+
|
|
18
|
+
export interface ScanSubmit {
|
|
19
|
+
uuid: string
|
|
20
|
+
result: string
|
|
21
|
+
api: string
|
|
22
|
+
visibility: string
|
|
23
|
+
message: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface ScanVerdict {
|
|
27
|
+
malicious: boolean
|
|
28
|
+
phishing: boolean
|
|
29
|
+
score: number
|
|
30
|
+
tags: string[]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ScanResult {
|
|
34
|
+
uuid: string
|
|
35
|
+
url: string
|
|
36
|
+
ip: string
|
|
37
|
+
country: string
|
|
38
|
+
malicious: boolean
|
|
39
|
+
phishing: boolean
|
|
40
|
+
score: number
|
|
41
|
+
reportUrl: string
|
|
42
|
+
screenshot: string
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Submit a URL for scanning. Returns the UUID — call getResult() after ~30s. */
|
|
46
|
+
export async function submitScan(url: string, visibility: Visibility = 'unlisted'): Promise<ScanSubmit> {
|
|
47
|
+
requireApiKey('URLSCAN_API_KEY')
|
|
48
|
+
return skill.post('/scan/', { url, visibility })
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Retrieve a completed scan by UUID. */
|
|
52
|
+
export async function getResult(uuid: string): Promise<ScanResult> {
|
|
53
|
+
requireApiKey('URLSCAN_API_KEY')
|
|
54
|
+
|
|
55
|
+
const raw = await skill.get(`/result/${uuid}/`)
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
uuid,
|
|
59
|
+
url: raw.page?.url ?? '',
|
|
60
|
+
ip: raw.page?.ip ?? '',
|
|
61
|
+
country: raw.page?.country ?? '',
|
|
62
|
+
malicious: raw.verdicts?.overall?.malicious ?? false,
|
|
63
|
+
phishing: raw.verdicts?.overall?.phishing ?? false,
|
|
64
|
+
score: raw.verdicts?.overall?.score ?? 0,
|
|
65
|
+
reportUrl: `https://urlscan.io/result/${uuid}/`,
|
|
66
|
+
screenshot: `https://urlscan.io/screenshots/${uuid}.png`,
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Submit and poll for result (waits up to maxWaitMs). */
|
|
71
|
+
export async function scanAndWait(
|
|
72
|
+
url: string,
|
|
73
|
+
visibility: Visibility = 'unlisted',
|
|
74
|
+
maxWaitMs = 60_000,
|
|
75
|
+
): Promise<ScanResult> {
|
|
76
|
+
requireApiKey('URLSCAN_API_KEY')
|
|
77
|
+
|
|
78
|
+
const submit = await submitScan(url, visibility)
|
|
79
|
+
const uuid = submit.uuid
|
|
80
|
+
const poll = 10_000
|
|
81
|
+
const start = Date.now()
|
|
82
|
+
|
|
83
|
+
while (Date.now() - start < maxWaitMs) {
|
|
84
|
+
await new Promise(r => setTimeout(r, poll))
|
|
85
|
+
try {
|
|
86
|
+
return await getResult(uuid)
|
|
87
|
+
} catch (e: any) {
|
|
88
|
+
// 404 means scan is still in progress
|
|
89
|
+
if (!e.message.includes('HTTP 404')) throw e
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
throw new Error(`urlscan: scan ${uuid} did not complete within ${maxWaitMs / 1000}s`)
|
|
94
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "urlscan",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Submit URLs for automated malware and phishing analysis, then retrieve safety verdicts and screenshots via urlscan.io",
|
|
5
|
+
"author": "aiden",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"tools": [],
|
|
8
|
+
"trigger_phrases": [],
|
|
9
|
+
"compatible_agents": [
|
|
10
|
+
"aiden"
|
|
11
|
+
],
|
|
12
|
+
"min_agent_version": "3.0.0",
|
|
13
|
+
"tags": [
|
|
14
|
+
"security",
|
|
15
|
+
"malware",
|
|
16
|
+
"phishing",
|
|
17
|
+
"url",
|
|
18
|
+
"scan",
|
|
19
|
+
"osint",
|
|
20
|
+
"urlscan",
|
|
21
|
+
"threat-intel"
|
|
22
|
+
],
|
|
23
|
+
"created": "2026-04-27T17:11:41.053Z"
|
|
24
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: virustotal
|
|
3
|
+
description: Check file hashes, URLs, domains, and IP addresses against 70+ antivirus engines and threat intelligence feeds via VirusTotal
|
|
4
|
+
category: security
|
|
5
|
+
version: 1.0.0
|
|
6
|
+
license: Apache-2.0
|
|
7
|
+
origin: aiden
|
|
8
|
+
tags: security, virustotal, malware, antivirus, threat-intel, hash, url, domain, ip, incident-response
|
|
9
|
+
env_required:
|
|
10
|
+
- VIRUSTOTAL_API_KEY
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# VirusTotal — Threat Intelligence
|
|
14
|
+
|
|
15
|
+
VirusTotal aggregates results from 70+ antivirus engines and threat intelligence feeds. Check file hashes, URLs, domains, and IP addresses for known malware, phishing, and suspicious activity.
|
|
16
|
+
|
|
17
|
+
**Requires:** `VIRUSTOTAL_API_KEY` — free at https://www.virustotal.com/gui/my-apikey (4 req/min on free tier).
|
|
18
|
+
|
|
19
|
+
## When to Use
|
|
20
|
+
|
|
21
|
+
- User has a file hash and wants to know if it is malware
|
|
22
|
+
- User has a suspicious URL and wants a reputation check
|
|
23
|
+
- User is investigating an incident and needs threat context for a domain or IP
|
|
24
|
+
- User asks "is this hash malicious?", "check this URL", or "is this domain flagged?"
|
|
25
|
+
- SOC/IR workflow: triage IOCs (indicators of compromise) quickly
|
|
26
|
+
|
|
27
|
+
## How to Use
|
|
28
|
+
|
|
29
|
+
### Check a file hash (MD5, SHA-1, or SHA-256)
|
|
30
|
+
|
|
31
|
+
```powershell
|
|
32
|
+
$hash = "44d88612fea8a8f36de82e1278abb02f" # EICAR test file
|
|
33
|
+
$key = $env:VIRUSTOTAL_API_KEY
|
|
34
|
+
$url = "https://www.virustotal.com/api/v3/files/$hash"
|
|
35
|
+
|
|
36
|
+
$result = Invoke-RestMethod -Uri $url -Headers @{ "x-apikey" = $key }
|
|
37
|
+
$stats = $result.data.attributes.last_analysis_stats
|
|
38
|
+
Write-Host "File: $($result.data.attributes.meaningful_name)"
|
|
39
|
+
Write-Host "Malicious: $($stats.malicious) / $($stats.malicious + $stats.undetected + $stats.harmless)"
|
|
40
|
+
Write-Host "Suspicious: $($stats.suspicious)"
|
|
41
|
+
Write-Host "First seen: $($result.data.attributes.first_submission_date)"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Check a URL for malware or phishing
|
|
45
|
+
|
|
46
|
+
```powershell
|
|
47
|
+
$targetUrl = "https://example.com"
|
|
48
|
+
$key = $env:VIRUSTOTAL_API_KEY
|
|
49
|
+
|
|
50
|
+
# URL id = URL-safe base64 without padding
|
|
51
|
+
$bytes = [System.Text.Encoding]::UTF8.GetBytes($targetUrl)
|
|
52
|
+
$b64 = [Convert]::ToBase64String($bytes).Replace('+','-').Replace('/','_').TrimEnd('=')
|
|
53
|
+
$url = "https://www.virustotal.com/api/v3/urls/$b64"
|
|
54
|
+
|
|
55
|
+
$result = Invoke-RestMethod -Uri $url -Headers @{ "x-apikey" = $key }
|
|
56
|
+
$stats = $result.data.attributes.last_analysis_stats
|
|
57
|
+
Write-Host "URL: $targetUrl"
|
|
58
|
+
Write-Host "Malicious: $($stats.malicious)"
|
|
59
|
+
Write-Host "Phishing: $($result.data.attributes.categories -join ', ')"
|
|
60
|
+
Write-Host "Reputation: $($result.data.attributes.reputation)"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Check a domain's reputation
|
|
64
|
+
|
|
65
|
+
```powershell
|
|
66
|
+
$domain = "example.com"
|
|
67
|
+
$key = $env:VIRUSTOTAL_API_KEY
|
|
68
|
+
$url = "https://www.virustotal.com/api/v3/domains/$domain"
|
|
69
|
+
|
|
70
|
+
$result = Invoke-RestMethod -Uri $url -Headers @{ "x-apikey" = $key }
|
|
71
|
+
$attrs = $result.data.attributes
|
|
72
|
+
Write-Host "Domain: $domain"
|
|
73
|
+
Write-Host "Reputation: $($attrs.reputation)"
|
|
74
|
+
Write-Host "Categories: $($attrs.categories.PSObject.Properties.Value -join ', ')"
|
|
75
|
+
$stats = $attrs.last_analysis_stats
|
|
76
|
+
Write-Host "Malicious: $($stats.malicious) engines"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Check an IP address
|
|
80
|
+
|
|
81
|
+
```powershell
|
|
82
|
+
$ip = "1.1.1.1"
|
|
83
|
+
$key = $env:VIRUSTOTAL_API_KEY
|
|
84
|
+
$url = "https://www.virustotal.com/api/v3/ip_addresses/$ip"
|
|
85
|
+
|
|
86
|
+
$result = Invoke-RestMethod -Uri $url -Headers @{ "x-apikey" = $key }
|
|
87
|
+
$attrs = $result.data.attributes
|
|
88
|
+
$stats = $attrs.last_analysis_stats
|
|
89
|
+
Write-Host "IP: $ip"
|
|
90
|
+
Write-Host "AS Owner: $($attrs.as_owner)"
|
|
91
|
+
Write-Host "Country: $($attrs.country)"
|
|
92
|
+
Write-Host "Reputation: $($attrs.reputation)"
|
|
93
|
+
Write-Host "Malicious: $($stats.malicious) engines"
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Examples
|
|
97
|
+
|
|
98
|
+
**"Is hash 44d88612fea8a8f36de82e1278abb02f malware?"**
|
|
99
|
+
→ Use the file hash check — malicious count > 0 means confirmed threats.
|
|
100
|
+
|
|
101
|
+
**"Check if https://suspicious-login.com is phishing"**
|
|
102
|
+
→ Use the URL check — look at `malicious` and `phishing` categories.
|
|
103
|
+
|
|
104
|
+
**"I got an alert for domain evil-c2.net — is it known bad?"**
|
|
105
|
+
→ Use the domain check — look at reputation score and malicious engine count.
|
|
106
|
+
|
|
107
|
+
**"This IP keeps hitting our firewall — is it a known attacker?"**
|
|
108
|
+
→ Use the IP check — `reputation < 0` and high malicious count = treat as threat.
|
|
109
|
+
|
|
110
|
+
## Cautions
|
|
111
|
+
|
|
112
|
+
- Free tier rate limit: 4 requests per minute — add `Start-Sleep -Seconds 16` between calls when checking multiple IOCs
|
|
113
|
+
- VirusTotal results reflect last scan time; for fresh analysis you must submit (POST /files or POST /urls) — not covered here
|
|
114
|
+
- A clean result (0 detections) does not guarantee safety — new malware may not yet be in the database
|
|
115
|
+
- File hashes are public — never submit sensitive files directly; only submit the hash
|
|
116
|
+
- Reputation score: positive = clean, 0 = neutral, negative = suspicious/malicious
|
|
117
|
+
|
|
118
|
+
## Requirements
|
|
119
|
+
|
|
120
|
+
- `VIRUSTOTAL_API_KEY` — free account at https://www.virustotal.com/gui/join-us
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// skills/virustotal/index.ts
|
|
2
|
+
// Programmatic handler — file, URL, domain, and IP reputation via VirusTotal v3 API.
|
|
3
|
+
|
|
4
|
+
import { ApiSkill, requireApiKey } from '../../core/apiSkillBase'
|
|
5
|
+
|
|
6
|
+
const skill = new ApiSkill({
|
|
7
|
+
name: 'virustotal',
|
|
8
|
+
baseUrl: 'https://www.virustotal.com/api/v3',
|
|
9
|
+
apiKeyEnv: 'VIRUSTOTAL_API_KEY',
|
|
10
|
+
authType: 'header',
|
|
11
|
+
authHeader: 'x-apikey',
|
|
12
|
+
rateLimit: { requests: 4, windowMs: 60_000 },
|
|
13
|
+
timeout: 20_000,
|
|
14
|
+
retries: 3,
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
export interface AnalysisStats {
|
|
18
|
+
malicious: number
|
|
19
|
+
suspicious: number
|
|
20
|
+
undetected: number
|
|
21
|
+
harmless: number
|
|
22
|
+
timeout: number
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface VTReport {
|
|
26
|
+
id: string
|
|
27
|
+
name: string
|
|
28
|
+
reputation: number
|
|
29
|
+
stats: AnalysisStats
|
|
30
|
+
categories: string[]
|
|
31
|
+
lastAnalysis: string
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Encode a URL to the VirusTotal URL identifier (URL-safe base64, no padding). */
|
|
35
|
+
function urlId(url: string): string {
|
|
36
|
+
return Buffer.from(url).toString('base64url')
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function extractStats(attrs: any): AnalysisStats {
|
|
40
|
+
const s = attrs?.last_analysis_stats ?? {}
|
|
41
|
+
return {
|
|
42
|
+
malicious: s.malicious ?? 0,
|
|
43
|
+
suspicious: s.suspicious ?? 0,
|
|
44
|
+
undetected: s.undetected ?? 0,
|
|
45
|
+
harmless: s.harmless ?? 0,
|
|
46
|
+
timeout: s.timeout ?? 0,
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function extractCategories(attrs: any): string[] {
|
|
51
|
+
if (!attrs?.categories) return []
|
|
52
|
+
if (Array.isArray(attrs.categories)) return attrs.categories as string[]
|
|
53
|
+
// categories can be an object of { engine: category } — return unique values
|
|
54
|
+
return [...new Set(Object.values(attrs.categories) as string[])]
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Check a file hash (MD5, SHA-1, or SHA-256) against VirusTotal. */
|
|
58
|
+
export async function fileReport(hash: string): Promise<VTReport> {
|
|
59
|
+
requireApiKey('VIRUSTOTAL_API_KEY')
|
|
60
|
+
|
|
61
|
+
const raw = await skill.get(`/files/${encodeURIComponent(hash)}`)
|
|
62
|
+
const attrs = raw.data?.attributes ?? {}
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
id: raw.data?.id ?? hash,
|
|
66
|
+
name: attrs.meaningful_name ?? attrs.name ?? hash,
|
|
67
|
+
reputation: attrs.reputation ?? 0,
|
|
68
|
+
stats: extractStats(attrs),
|
|
69
|
+
categories: extractCategories(attrs),
|
|
70
|
+
lastAnalysis: attrs.last_analysis_date ?? '',
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Check a URL's reputation via VirusTotal. */
|
|
75
|
+
export async function urlReport(url: string): Promise<VTReport> {
|
|
76
|
+
requireApiKey('VIRUSTOTAL_API_KEY')
|
|
77
|
+
|
|
78
|
+
const id = urlId(url)
|
|
79
|
+
const raw = await skill.get(`/urls/${id}`)
|
|
80
|
+
const attrs = raw.data?.attributes ?? {}
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
id,
|
|
84
|
+
name: url,
|
|
85
|
+
reputation: attrs.reputation ?? 0,
|
|
86
|
+
stats: extractStats(attrs),
|
|
87
|
+
categories: extractCategories(attrs),
|
|
88
|
+
lastAnalysis: attrs.last_analysis_date ?? '',
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Check a domain's reputation via VirusTotal. */
|
|
93
|
+
export async function domainReport(domain: string): Promise<VTReport> {
|
|
94
|
+
requireApiKey('VIRUSTOTAL_API_KEY')
|
|
95
|
+
|
|
96
|
+
const raw = await skill.get(`/domains/${encodeURIComponent(domain)}`)
|
|
97
|
+
const attrs = raw.data?.attributes ?? {}
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
id: raw.data?.id ?? domain,
|
|
101
|
+
name: domain,
|
|
102
|
+
reputation: attrs.reputation ?? 0,
|
|
103
|
+
stats: extractStats(attrs),
|
|
104
|
+
categories: extractCategories(attrs),
|
|
105
|
+
lastAnalysis: attrs.last_analysis_date ?? '',
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Check an IP address reputation via VirusTotal. */
|
|
110
|
+
export async function ipReport(ip: string): Promise<VTReport> {
|
|
111
|
+
requireApiKey('VIRUSTOTAL_API_KEY')
|
|
112
|
+
|
|
113
|
+
const raw = await skill.get(`/ip_addresses/${encodeURIComponent(ip)}`)
|
|
114
|
+
const attrs = raw.data?.attributes ?? {}
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
id: raw.data?.id ?? ip,
|
|
118
|
+
name: ip,
|
|
119
|
+
reputation: attrs.reputation ?? 0,
|
|
120
|
+
stats: extractStats(attrs),
|
|
121
|
+
categories: extractCategories(attrs),
|
|
122
|
+
lastAnalysis: attrs.last_analysis_date ?? '',
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "virustotal",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Check file hashes, URLs, domains, and IP addresses against 70+ antivirus engines and threat intelligence feeds via VirusTotal",
|
|
5
|
+
"author": "aiden",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"tools": [],
|
|
8
|
+
"trigger_phrases": [],
|
|
9
|
+
"compatible_agents": [
|
|
10
|
+
"aiden"
|
|
11
|
+
],
|
|
12
|
+
"min_agent_version": "3.0.0",
|
|
13
|
+
"tags": [
|
|
14
|
+
"security",
|
|
15
|
+
"virustotal",
|
|
16
|
+
"malware",
|
|
17
|
+
"antivirus",
|
|
18
|
+
"threat-intel",
|
|
19
|
+
"hash",
|
|
20
|
+
"url",
|
|
21
|
+
"domain",
|
|
22
|
+
"ip",
|
|
23
|
+
"incident-response"
|
|
24
|
+
],
|
|
25
|
+
"created": "2026-04-27T17:11:41.082Z"
|
|
26
|
+
}
|