kastell-research — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited kastell-research (Agent Skill) and scored it 45/100 (orange). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 2 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 2 flagged
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.
*.sig, SIGNATURES) outside the documentation.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.
*.sig, SIGNATURES) outside the documentation.Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Explore the Kastell codebase using read-only tools (Read, Grep, Glob). Runs in a forked Explore agent with Kastell architecture knowledge inlined.
Commands: !node -e "import('fs').then(f=>console.log(f.readdirSync('src/commands').filter(x=>x.endsWith('.ts')).map(x=>x.replace('.ts','')).join(', '))).catch(()=>console.log('commands dir not found'))" Provider registry: !node -e "import('fs').then(f=>{const c=f.readFileSync('src/constants.ts','utf8');const m=c.match(/PROVIDER_REGISTRY[\s\S]{0,200}/);console.log(m?m[0].split('\n').slice(0,4).join('\n'):'not found')}).catch(()=>console.log('constants.ts not found'))"
src/
commands/ # 31 thin CLI wrappers (parse args + delegate only)
core/ # Business logic (ALL computation here)
audit/ # 30 audit categories, 457+ checks
lock/ # 24-step server hardening
providers/ # Cloud API: hetzner, digitalocean, vultr, linode
adapters/ # Platform abstraction: coolify, dokploy
factory.ts # getAdapter(platform) — entry point
mcp/
server.ts # 13 tool registrations
tools/ # Handler files
utils/ # ssh, config, cloudInit, modeGuard
types/ # ServerMode, ServerRecord, Platform
constants.ts # PROVIDER_REGISTRYCommands (parse args) --> Core (business logic) --> Providers (cloud API) / Adapters (platform ops). MCP tools also delegate to Core.
Bug investigation:
src/commands/<name>.ts)src/core/<name>.ts)Feature mapping:
__tests__/Architecture question:
kastell-plugin/skills/kastell-ops/SKILL.md for full detail (adapter contract, provider registry, layer rules)Common failure patterns and where to look first:
| Symptom | Start Here | Then Check |
|---|---|---|
| SSH auth failure | src/utils/ssh.ts → sshExec() | assertValidIp(), server config ~/.kastell/servers.json, banner parsing |
| Provider API error | src/providers/<name>.ts | withProviderErrorHandling() in src/utils/retry.ts, API token config |
| Audit check false positive | src/core/audit/checks/<category>.ts | SSH command output parsing, regex pattern, sshExec mock in test |
| Fix rejected (SAFE tier) | src/core/fix.ts → resolveTier() | FORBIDDEN_PATTERNS, shell redirect/pipe in fixCommand string |
| MCP tool error | src/mcp/tools/<name>.ts | Handler → core delegation, Zod schema validation, result.content format |
| Lock step failure | src/core/lock.ts | Step's SSH command, sshExec stderr, cloud-init completion |
| Config not found | src/utils/config.ts | ~/.kastell/ dir existence, servers.json format, migration from ~/.quicklify/ |
Known pitfalls: See kastell-plugin/skills/kastell-ops/references/pitfalls.md
$ARGUMENTS
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.