Back to skill

Security audit

@blockchain-forever/aelf-skills

Security checks for vulnerabilities and agentic risk

Overview

This is a coherent aelf skill installer hub, but its bootstrap path can delete unintended directories from catalog-controlled IDs and runs dependency installation on downloaded third-party code by default.

Install only if you trust the catalog and upstream skill packages. Prefer `--skip-install`, avoid custom catalogs from untrusted sources, use a disposable destination directory, and review any downloaded skill before running setup or blockchain write operations such as transfers, approvals, swaps, trades, or contract sends.

Vulnerability Patterns
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

T03 · Remote Payload Retrieval and Execution

Error
Location
scripts/bootstrap.ts:267
Finding
Unverified Remote Code Is Downloaded and Executed During Bootstrap<![CDATA[ ## Vulnerability Details **File Location**: `scripts/bootstrap.ts:267-350` **Vulnerability Type**: Remote payload retrieval and insecure dependency installation **Risk Level**: High ### Vulnerable Code ```ts function downloadViaNpm(skill: SkillCatalogEntry, targetDir: string): { ok: boolean; message: string } { const tmpDir = mkdtempSync(path.join(os.tmpdir(), 'aelf-skills-pack-')); const packageRef = `${skill.npm.name}@${skill.npm.version}`; const packResult = runCommand('npm', ['pack', packageRef, '--pack-destination', tmpDir]); if (!packResult.ok) { return { ok: false, message: packResult.stderr || packResult.stdout || `npm pack failed for ${packageRef}`, }; } const lines = packResult.stdout .split(/\r?\n/) .map(value => value.trim()) .filter(Boolean); const tarballName = lines[lines.length - 1]; const tarballPath = path.join(tmpDir, tarballName); const extractResult = runCommand('tar', ['-xzf', tarballPath, '-C', targetDir, '--strip-components=1']); if (!extractResult.ok) { return { ok: false, message: extractResult.stderr || extractResult.stdout || `tar extract failed for ${packageRef}`, }; } rmSync(tmpDir, { recursive: true, force: true }); return { ok: true, message: `downloaded via npm: ${packageRef}`, }; } function downloadViaGithub(skill: SkillCatalogEntry, targetDir: string): { ok: boolean; message: string } { if (!skill.repository.https) { return { ok: false, message: 'repository.https is missing', }; } const cloneResult = runCommand('git', ['clone', '--depth', '1', skill.repository.https, targetDir]); if (!cloneResult.ok) { return { ok: false, message: cloneResult.stderr || cloneResult.stdout || `git clone failed for ${skill.repository.https}`, }; } return { ok: true, message: `downloaded via github: ${skill.repository.https}`, }; } function installSkillDependencies(skillDir: str ...[truncated 2795 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Validate every catalog against a strict schema before using it. 2. Permit only explicitly approved npm scopes, package names, repository hosts, and repository owners. 3. Pin GitHub sources to reviewed commit hashes rather than cloning a mutable default branch. 4. Record and verify npm tarball integrity hashes or trusted package provenance before extraction. 5. Treat custom catalogs as untrusted and require an explicit high-risk confirmation before downloading or installing their entries. 6. Disable dependency lifecycle scripts by default using the package manager's supported safe-install option. Require a separate, explicit opt-in when scripts are necessary. 7. Perform installation in a sandbox or container with minimal filesystem access, no inherited secrets, and restricted network access. 8. Audit the downloaded package manifest and lockfile before dependency installation. 9. Prefer lockfile-based, frozen dependency resolution so transitive packages cannot silently change. 10. Clearly document that omitting `--skip-install` executes third-party installation logic. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/bootstrap.ts:189
Finding
Catalog-Controlled Skill ID Can Escape the Destination and Trigger Recursive Deletion<![CDATA[ ## Vulnerability Details **File Location**: `scripts/bootstrap.ts:189-201` **Vulnerability Type**: Path traversal leading to arbitrary recursive deletion **Risk Level**: High ### Vulnerable Code ```ts function downloadSkill(skill: SkillCatalogEntry, destRoot: string, source: SourceMode): SkillRunResult { const targetDir = path.join(destRoot, skill.id); const result: SkillRunResult = { id: skill.id, download: 'failed', install: 'skipped', issues: [], }; rmSync(targetDir, { recursive: true, force: true }); mkdirSync(targetDir, { recursive: true }); ``` The same unvalidated path is deleted again during npm-to-GitHub fallback: ```ts rmSync(targetDir, { recursive: true, force: true }); mkdirSync(targetDir, { recursive: true }); ``` ### Technical Analysis `skill.id` is loaded from `skills-catalog.json` or from a user-supplied `--catalog` file and is used directly as a path component. There is no runtime schema validation, safe identifier pattern, normalization check, or containment check before `targetDir` is passed to `rmSync` with both `recursive` and `force` enabled. A malicious identifier containing traversal segments, such as `../../target`, causes the normalized target path to escape `destRoot`. An absolute or otherwise platform-specific path may create similar risks depending on path resolution semantics. The escaped directory is recursively deleted before any package download is attempted, so exploitation does not depend on control of npm, GitHub, or package installation. The fallback path at `scripts/bootstrap.ts:245-246` repeats the same destructive operation and should be protected by the same validation. ### Attack Path 1. An attacker creates or modifies a catalog containing a skill entry whose `id` includes path traversal, for example `../../important-directory`. 2. The attacker persuades the user or automation to invoke bootstrap with that catalog, or modifies a catalog already trusted by the workflow. 3. ...[truncated 1159 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Enforce a restrictive identifier format before using `skill.id`, for example: ```ts const SAFE_SKILL_ID = /^[a-z0-9][a-z0-9-]*$/; if (!SAFE_SKILL_ID.test(skill.id)) { throw new Error(`Invalid skill id: ${skill.id}`); } ``` 2. Resolve and verify the target path before every deletion or write: ```ts const root = path.resolve(destRoot); const target = path.resolve(root, skill.id); const relative = path.relative(root, target); if ( relative === '' || relative.startsWith(`..${path.sep}`) || relative === '..' || path.isAbsolute(relative) ) { throw new Error(`Skill path escapes destination root: ${skill.id}`); } ``` 3. Perform this containment check immediately before each `rmSync`, including the npm-to-GitHub fallback deletion. 4. Refuse to delete the filesystem root, destination root, current working directory, user home directory, or other protected locations. 5. Validate custom and generated catalogs against a strict runtime schema that constrains every skill ID. 6. Consider creating a fresh per-run staging directory instead of recursively deleting a path derived from catalog data. 7. Add automated tests covering `..`, nested traversal, absolute paths, path separators, empty IDs, encoded separators, and platform-specific Windows path forms. ]]>
Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (11)

Ae1

High
Category
analysis-evasion
Content
Read `skills-catalog.json` for the full machine-readable registry. Current skills:
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Credential Access

High
Category
Privilege Escalation
Content
): CommandResult {
  const child = spawnSync(command, args, {
    cwd,
    env: env ? { ...process.env, ...env } : process.env,
    stdio: 'pipe',
    encoding: 'utf8',
  });
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Lp3

Medium
Category
MCP Least Privilege
Confidence
78% confidence
Finding
The skill provides operational shell commands and setup workflows that imply execution and environment interaction, but it does not declare an explicit tool scope such as permissions or allowed-tools. In a meta-skill that bootstraps and configures other skills, this ambiguity can cause an agent to overreach, invoke unintended tools, or operate with broader access than the user expects.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The bootstrap flow unconditionally deletes `targetDir` with `rmSync(..., { recursive: true, force: true })` and recreates it, which can destroy existing local content without confirmation or path safety checks. In a tool whose purpose is to download and bootstrap many skills into a user-supplied destination, this increases the chance of accidental data loss if a catalog entry, skill ID, or destination path is wrong or manipulated.

Missing User Warnings

Medium
Confidence
98% confidence
Finding
Running `bun install` on freshly downloaded skill content executes package-manager install behavior on untrusted third-party code, which can trigger lifecycle scripts or other dependency-fetch side effects. In this skill hub context, the script is explicitly designed to fetch code from npm/GitHub/local catalogs, so it meaningfully expands the attack surface to arbitrary code execution on the operator's machine if a package, repository, or catalog entry is compromised.

Context-Inappropriate Capability

Medium
Confidence
84% confidence
Finding
The manifest describes a hub for discovering, installing, routing, and auditing aelf skills. While some command invocation may be expected for installation workflows, this utility exposes a reusable arbitrary command runner and command-existence checker over any binary, which is a broader capability than the file's documented hub-oriented purpose justifies on its own.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The runCommand function executes subprocesses via spawnSync, which is a safety-relevant operation for code files. In this file there is no confirmation prompt, logging, print statement, or explanatory comment/docstring disclosing that commands will be executed.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The catalog advertises skills capable of transfers, swaps, approvals, contract execution, and other asset-moving operations, but the manifest provides no explicit warning that these actions may be irreversible or financially risky. In a discovery/install hub, this omission can cause users or downstream agents to treat high-risk skills like ordinary read-only utilities, increasing the chance of unintended wallet actions or unsafe automated routing.

Scope Creep

Low
Category
Excessive Agency
Content
Recommended style:
1. Start with action verbs (for example `Query block status`, `Create wallet`).
2. One capability sentence should describe one action.
3. Avoid vague wording such as `handle everything`.
4. Include boundary hints such as `read-only` and `simulate/send`.

## 6. Modes and compatibility
Confidence
70% confidence
Finding
Skill's behavior or capabilities extend beyond its stated purpose. Scope creep allows an agent to perform actions unrelated to its documented functionality, increasing the attack surface.

Missing User Warnings

Low
Confidence
82% confidence
Finding
The writeJsonFile function creates directories and writes files, which is a safety-critical filesystem modification under the audit criteria. This code includes no user-facing notice, confirmation, or explanatory comment/docstring indicating that it will write to disk.

Context-Inappropriate Capability

Low
Confidence
72% confidence
Finding
The manifest focuses on listing, installing, routing, and auditing skills. This helper reads process environment variables from caller-supplied path templates, which introduces access to host configuration data beyond the core parsing and manifest-handling responsibilities shown elsewhere in the file.

Static analysis

Detected: suspicious.dangerous_exec

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
scripts/lib/utils.ts:32