Back to skill

Security audit

Eval Skills

Security checks for vulnerabilities and agentic risk

Overview

This skill is a coherent evaluation toolkit, but it can run evaluated skills and custom scorers with more host access than users may expect from its sandbox language.

Install only if you are comfortable treating evaluated local skills, MCP stdio servers, and custom scorer files as trusted code unless you force strong container isolation. Prefer Docker mode on a hardened runner without sensitive credentials, avoid untrusted benchmarks with customScorerPath, and do not run this against third-party skills on a workstation or CI account with broad secrets.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (2)

T09 · Insecure Skill Coding Practices

Error
Location
packages/core/src/evaluator/scorers/CustomScorer.ts:23
Finding
Arbitrary Host Code Execution Through Unisolated Custom Scorers<![CDATA[ ## Vulnerability Details **File Location**: `packages/core/src/evaluator/scorers/CustomScorer.ts:23-53` **Vulnerability Type**: Unrestricted dynamic module loading and execution **Risk Level**: High ### Vulnerable Code ```ts const scorerPath = path.resolve(expected.customScorerPath); const workerCode = ` const { parentPort, workerData } = require('node:worker_threads'); const { pathToFileURL } = require('node:url'); async function run() { try { const { scorerPath, output, expected } = workerData; const modulePath = pathToFileURL(scorerPath).href; const scorerModule = await import(modulePath); const scorerFn = scorerModule.default; if (typeof scorerFn !== 'function') { throw new Error('Default export is not a function'); } const result = await scorerFn(output, expected); parentPort.postMessage({ success: true, result }); } catch (error) { parentPort.postMessage({ success: false, error: error.message }); } } run(); `; return new Promise<ScorerResult>((resolve) => { const worker = new Worker(workerCode, { eval: true, workerData: { scorerPath, output, expected }, resourceLimits: { maxOldGenerationSizeMb: 128 }, }); ``` ### Technical Analysis The custom scorer path originates from `expected.customScorerPath`, is converted into an absolute path with `path.resolve()`, and is then dynamically imported. The implementation does not canonicalize the resulting path against an approved scorer directory, require an explicit trust decision, or execute the module inside the project’s Docker sandbox. A Node.js worker thread is a concurrency mechanism, not a security boundary. Code loaded by the worker retains access to Node.js capabilities such as: - Reading and modifying files accessible to the current user - Reading process environment variables - Creating network connections - Starting child processes - Accessing other resources available ...[truncated 1701 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Disable custom scorers by default and require an explicit command-line or configuration opt-in that warns users that scorer code is trusted executable code. 2. Resolve the scorer path using `fs.realpath()` and verify that it remains inside a dedicated, administrator-approved scorer directory. Reject absolute paths, traversal, symlink escapes, and paths outside that root. 3. Execute custom scorers in the Docker sandbox rather than a worker thread. 4. Configure the scorer container with: - No network access - A read-only root filesystem - A narrowly scoped read-only mount for the scorer - No host environment credentials - A non-root user - Dropped Linux capabilities - Resource and execution-time limits - The restrictive seccomp profile 5. Pass only the output and minimum expected scoring fields into the isolated process. 6. Validate the scorer result against a strict schema before accepting it. 7. Document that custom scorer files are executable code and must not be loaded from untrusted benchmarks. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
packages/core/src/adapters/SubprocessAdapter.ts:62
Finding
Untrusted Subprocess Skills Execute on the Host with Ineffective Network and Filesystem Policies<![CDATA[ ## Vulnerability Details **File Locations**: - `packages/core/src/adapters/SubprocessAdapter.ts:62-67` - `packages/core/src/sandbox/ProcessSandbox.ts:47-55` - `packages/core/src/sandbox/ProcessSandbox.ts:83-90` **Vulnerability Type**: Weak sandbox used as the default execution boundary **Risk Level**: High ### Vulnerable Code Default selection of the process sandbox: ```ts constructor(config?: SubprocessAdapterConfig) { super(); this.sandboxLevel = config?.sandboxLevel ?? "process"; this.customSandboxConfig = config?.sandboxConfig; this.sandboxFactory = new SandboxFactory(); } ``` The configured no-network policy is not enforced: ```ts if (this.config.network === "none") { this.emit("security-warning", { message: "ProcessSandbox cannot enforce network isolation. " + "Use DockerSandbox for strict network control.", skillDir, }); } ``` The evaluated interpreter is then started directly on the host: ```ts const child = spawn(finalExec, finalArgs, { cwd, env: safeEnv, stdio: ["pipe", "pipe", "pipe"], detached: true, }); ``` ### Technical Analysis The standard subprocess adapter defaults to `sandboxLevel: "process"`. Process mode launches an allowlisted interpreter directly on the host under the evaluator’s operating-system identity. The entrypoint validator reduces command-string injection and filters sensitive environment variable names, but it does not constrain what an accepted Python, Node.js, Ruby, PHP, Java, Bash, or other script does after execution begins. For example, an accepted `python3 skill.py` entrypoint can perform unrestricted file reads, file writes, process execution, and network operations from inside `skill.py`. Although the default sandbox policy declares no network access and a read-only filesystem, `ProcessSandbox` does not enforce those policies at the operating-system level. When network policy is `none`, it only emits a warning and continues execution. No mount namespace, filesystem na ...[truncated 1930 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Change the default subprocess runtime from `process` to `docker`. 2. Fail closed when Docker or another strong isolation backend is unavailable. Do not silently or automatically fall back to host-process execution for untrusted skills. 3. Require an explicit `--unsafe-process-sandbox` or equivalent opt-in before process mode can be used. 4. Display a prominent warning identifying process mode as arbitrary host code execution, not as a security sandbox. 5. For container execution, enforce: - `network: none` by default - A read-only root filesystem - Only the skill directory mounted, preferably read-only - A dedicated writable temporary directory with size limits - A non-root user with no supplementary groups - All Linux capabilities dropped - `no-new-privileges` - PID, memory, CPU, and output limits - The restrictive seccomp profile 6. Never mount SSH agents, Docker sockets, cloud credential directories, package-manager credentials, or the user’s home directory into the container. 7. If process mode must remain available, use operating-system isolation such as namespaces, seccomp, Landlock, AppArmor, SELinux, sandbox-exec, or an equivalent platform-specific mechanism. 8. Add integration tests that verify outbound connections fail and that files outside approved paths cannot be read or modified. ]]>
Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (181)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The declared description describes a unit testing and evaluation framework for AI skills, but the supplied code is a simple calculator-style skill. Its primary behavior is handling JSON-RPC requests to evaluate math expressions and perform basic string operations. It does not implement any of the described framework capabilities such as skill discovery, benchmarking, CI/CD quality gates, or report generation. This is a clear material mismatch in primary purpose and exposed capabilities.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The declared description says this skill is a unit testing framework for AI agent skills, with functionality around discovery, scaffolding, selection, evaluation, reporting, and CI/CD quality gates. The supplied code does none of that. Instead, it implements a standalone mock web search skill that reads a JSON-RPC request from stdin, handles 'invoke' and 'healthcheck', and returns canned answers from a hardcoded knowledge base with basic partial-word matching. This is a materially different primary purpose, not just an implementation detail, so the description does not accurately represent the code.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
The declared description emphasizes a unit testing/evaluation framework for assessing skill quality, benchmarking, CI/CD quality gates, and reporting. This code chunk instead implements a creation/scaffolding command that generates new skill skeletons from templates or OpenAPI specs and writes them to disk. While scaffolding is mentioned in the broad description, the specific declared purpose centers on evaluation/testing behavior, whereas this code’s primary function is code generation. That is a materially different capability from unit testing/evaluation, so this chunk does not accurately represent the declared description on its own.

eval() call detected

High
Category
Dangerous Code Execution
Content
# Safe subset of operations
        allowed = set("0123456789+-*/.() ")
        if all(c in allowed for c in expression):
            result = eval(expression)
            return {"result": str(result)}
        else:
            return {"result": "error: invalid characters in expression"}
Confidence
94% confidence
Finding
The skill uses Python eval() on user-controlled input. Although it restricts characters to digits, operators, periods, parentheses, and spaces, using eval for parsing remains unsafe-by-design and can still expose the process to denial-of-service through expensive expressions or unexpectedly permissive evaluation semantics; it also creates future risk if the allowlist is later expanded incorrectly.

Chaining Abuse

High
Category
Tool Misuse
Content
"lint:fix": "eslint packages/*/src --ext .ts --fix",
    "format": "prettier --write \"packages/*/src/**/*.ts\"",
    "typecheck": "tsc --noEmit -p tsconfig.base.json",
    "clean": "pnpm -r run clean && rm -rf node_modules"
  },
  "devDependencies": {
    "@typescript-eslint/eslint-plugin": "^7.0.0",
Confidence
75% confidence
Finding
Tool calls are chained to bypass individual safety checks or escalate capabilities beyond what any single tool call would allow.

Context-Inappropriate Capability

High
Confidence
97% confidence
Finding
The adapter treats any non-HTTP entrypoint as a local command and passes it to StdioClientTransport, which can spawn an arbitrary executable with attacker-controlled arguments. If untrusted skill definitions can supply entrypoints, this becomes direct local code execution in the host environment, which is especially dangerous in an evaluation framework that may process third-party skills.

Intent-Code Divergence

High
Confidence
98% confidence
Finding
The file advertises strong syscall filtering, but if the seccomp profile is missing it silently degrades to "unconfined", removing a major containment boundary while continuing execution. In a skill-evaluation sandbox, this materially increases the chance that untrusted code can access dangerous syscalls and exploit kernel/container-escape paths.

Docker Socket Access

High
Category
Privilege Escalation
Content
constructor(config: SandboxConfig) {
    super(config);
    this.docker = new Docker({
      socketPath: config.docker.socketPath ?? "/var/run/docker.sock",
    });
  }
Confidence
96% confidence
Finding
Connecting to /var/run/docker.sock gives this process powerful control over the host Docker daemon, which is effectively host-level privilege in many deployments. If an attacker can influence this component or its configuration, they may create privileged containers, mount the host filesystem, or otherwise escape intended sandbox boundaries completely.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
it("should reject command with shell injection characters", async () => {
    const sandbox = SandboxFactory.createProcessSandbox(TEST_CONFIG);
    const result = await sandbox.execute(
      "python3; rm -rf /",
      "/tmp",
      { jsonrpc: "2.0", method: "invoke", params: {}, id: 1 },
    );
Confidence
90% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Tool Parameter Abuse

High
Category
Tool Misuse
Content
it("should reject command with shell injection characters", async () => {
    const sandbox = SandboxFactory.createProcessSandbox(TEST_CONFIG);
    const result = await sandbox.execute(
      "python3; rm -rf /",
      "/tmp",
      { jsonrpc: "2.0", method: "invoke", params: {}, id: 1 },
    );
Confidence
90% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Tool Parameter Abuse

High
Category
Tool Misuse
Content
it("should reject command with shell injection characters", async () => {
    const sandbox = SandboxFactory.createProcessSandbox(TEST_CONFIG);
    const result = await sandbox.execute(
      "python3; rm -rf /",
      "/tmp",
      { jsonrpc: "2.0", method: "invoke", params: {}, id: 1 },
    );
Confidence
85% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Tool Parameter Abuse

High
Category
Tool Misuse
Content
it("should reject command with shell injection characters", async () => {
    const sandbox = SandboxFactory.createProcessSandbox(TEST_CONFIG);
    const result = await sandbox.execute(
      "python3; rm -rf /",
      "/tmp",
      { jsonrpc: "2.0", method: "invoke", params: {}, id: 1 },
    );
Confidence
90% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Chaining Abuse

High
Category
Tool Misuse
Content
it("should reject command with shell injection characters", async () => {
    const sandbox = SandboxFactory.createProcessSandbox(TEST_CONFIG);
    const result = await sandbox.execute(
      "python3; rm -rf /",
      "/tmp",
      { jsonrpc: "2.0", method: "invoke", params: {}, id: 1 },
    );
Confidence
75% confidence
Finding
Tool calls are chained to bypass individual safety checks or escalate capabilities beyond what any single tool call would allow.

Chaining Abuse

High
Category
Tool Misuse
Content
it("should reject command with shell injection characters", async () => {
    const sandbox = SandboxFactory.createProcessSandbox(TEST_CONFIG);
    const result = await sandbox.execute(
      "python3; rm -rf /",
      "/tmp",
      { jsonrpc: "2.0", method: "invoke", params: {}, id: 1 },
    );
Confidence
75% confidence
Finding
Tool calls are chained to bypass individual safety checks or escalate capabilities beyond what any single tool call would allow.

Credential Access

High
Category
Privilege Escalation
Content
it("should reject command with path traversal", async () => {
    const sandbox = SandboxFactory.createProcessSandbox(TEST_CONFIG);
    const result = await sandbox.execute(
      "python3 ../../etc/passwd",
      "/tmp/skill",
      { jsonrpc: "2.0", method: "invoke", params: {}, id: 1 },
    );
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
it("should reject command with path traversal", async () => {
    const sandbox = SandboxFactory.createProcessSandbox(TEST_CONFIG);
    const result = await sandbox.execute(
      "python3 ../../etc/passwd",
      "/tmp/skill",
      { jsonrpc: "2.0", method: "invoke", params: {}, id: 1 },
    );
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
it("should reject command with path traversal", async () => {
    const sandbox = SandboxFactory.createProcessSandbox(TEST_CONFIG);
    const result = await sandbox.execute(
      "python3 ../../etc/passwd",
      "/tmp/skill",
      { jsonrpc: "2.0", method: "invoke", params: {}, id: 1 },
    );
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
it("should reject command with path traversal", async () => {
    const sandbox = SandboxFactory.createProcessSandbox(TEST_CONFIG);
    const result = await sandbox.execute(
      "python3 ../../etc/passwd",
      "/tmp/skill",
      { jsonrpc: "2.0", method: "invoke", params: {}, id: 1 },
    );
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
it("should reject command with path traversal", async () => {
    const sandbox = SandboxFactory.createProcessSandbox(TEST_CONFIG);
    const result = await sandbox.execute(
      "python3 ../../etc/passwd",
      "/tmp/skill",
      { jsonrpc: "2.0", method: "invoke", params: {}, id: 1 },
    );
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
req = json.loads(sys.stdin.read())
# 尝试读取系统敏感文件
results = {}
sensitive_files = ["/etc/passwd", "/etc/shadow", "/root/.ssh/id_rsa", "/proc/1/environ"]
for f in sensitive_files:
    try:
        with open(f, "r") as fp:
Confidence
90% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
req = json.loads(sys.stdin.read())
# 尝试读取系统敏感文件
results = {}
sensitive_files = ["/etc/passwd", "/etc/shadow", "/root/.ssh/id_rsa", "/proc/1/environ"]
for f in sensitive_files:
    try:
        with open(f, "r") as fp:
Confidence
95% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
req = json.loads(sys.stdin.read())
# 尝试读取系统敏感文件
results = {}
sensitive_files = ["/etc/passwd", "/etc/shadow", "/root/.ssh/id_rsa", "/proc/1/environ"]
for f in sensitive_files:
    try:
        with open(f, "r") as fp:
Confidence
95% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
req = json.loads(sys.stdin.read())
# 尝试读取系统敏感文件
results = {}
sensitive_files = ["/etc/passwd", "/etc/shadow", "/root/.ssh/id_rsa", "/proc/1/environ"]
for f in sensitive_files:
    try:
        with open(f, "r") as fp:
Confidence
95% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
req = json.loads(sys.stdin.read())
# 尝试读取系统敏感文件
results = {}
sensitive_files = ["/etc/passwd", "/etc/shadow", "/root/.ssh/id_rsa", "/proc/1/environ"]
for f in sensitive_files:
    try:
        with open(f, "r") as fp:
Confidence
95% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
req = json.loads(sys.stdin.read())
# 尝试读取系统敏感文件
results = {}
sensitive_files = ["/etc/passwd", "/etc/shadow", "/root/.ssh/id_rsa", "/proc/1/environ"]
for f in sensitive_files:
    try:
        with open(f, "r") as fp:
Confidence
95% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Static analysis

Detected: suspicious.dangerous_exec, suspicious.dynamic_code_execution, suspicious.env_credential_access

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
packages/cli/src/commands/__tests__/eval.test.ts:44

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
packages/core/src/sandbox/ProcessSandbox.ts:85

Dynamic code execution detected.

Critical
Code
suspicious.dynamic_code_execution
Location
examples/skills/calculator/skill.py:31

Dynamic code execution detected.

Critical
Code
suspicious.dynamic_code_execution
Location
packages/core/src/sandbox/sandbox.integration.test.ts:333

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
packages/core/src/adapters/HttpAdapter.ts:82