# Fiftyone Eval Plugin

> Evaluates FiftyOne plugins for quality, security, and agent-readiness. Use when reviewing a plugin before installation, auditing an existing plugin, validating a plugin you just built, or assessing community plugins for safety. Produces a structured report with scores and actionable recommendations.

- Skill: `adonaivera/fiftyone-eval-plugin` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add adonaivera/fiftyone-eval-plugin`
- Raw SKILL.md: https://api.skillmd.com/api/skills/adonaivera/fiftyone-eval-plugin/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: AdonaiVera (https://skillmd.com/u/adonaivera)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/adonaivera/fiftyone-eval-plugin

---


# Evaluate FiftyOne Plugins

## Key Directives

**ALWAYS follow these rules:**

### 1. Read the entire plugin before judging
Read every source and configuration file of the plugin. Common file extensions: `.py`, `.ts`, `.tsx`, `.js`, `.yaml`, `.yml`, and `.json`. Never construct an assessment of a plugin without first reading every line of every source file.

### 2. Security first
Check for dangerous patterns before anything else. A plugin with perfect code quality that violates established and well known security best practices, is an immediate critical failure.

### 3. Be specific and actionable
Every finding must include: what's wrong, where it is (file + line), why it matters, and how to fix it. Never say "improve code quality" without pointing to the exact issue.

### 4. Score honestly
A plugin by a trusted org with security issues still gets a security failure. A community plugin with great code still gets credit. Judge the code, not the author.

### 5. Check the real plugin framework
Always verify the plugin under assessment against the actual FiftyOne plugin system. Use MCP tools to list operators, get schemas, and check registration — don't just read files.

```python
list_plugins(enabled=True)
list_operators(builtin_only=False)
get_operator_schema(operator_uri="@org/operator-name")
```

## Evaluation Workflow

### Phase 1: Manifest & Structure

Check the plugin is well-formed before loading it.

**1.1 Parse `fiftyone.yml`:**
- `name` exists and follows convention (`@org/plugin-name`)
- `version` is present and valid semver
- `fiftyone.version` constraint is specified
- `operators` and/or `panels` lists are non-empty
- `secrets` are declared if the plugin uses API keys

**1.2 Skills declaration:**
- Check if `fiftyone.yml` includes a `skills:` section listing companion skills (similar to `operators:` and `panels:`)
- If `skills:` is declared, verify each listed skill has a matching `SKILL.md` file in the plugin directory following the Agent Skills format (YAML frontmatter with `name` and `description`, plus workflow sections)
- If no `skills:` section exists, note this as an observation — the plugin does not ship with companion skills for AI assistants. This is not a penalty, but plugins with skills have higher agent success rates.

**1.3 File structure:**
- `__init__.py` exists
- Every operator in manifest has a matching class in the Python entry file
- Every panel in manifest has a matching class
- If panels declare: `dist/index.umd.js` exists (built JS bundle)
- `requirements.txt` exists if the plugin imports third-party packages (anything not in Python's standard library, packages that require `pip install`)

**1.3 Dependencies:**
- All Python imports resolve (`importlib.util.find_spec`)
- No pinned versions that conflict with FiftyOne's dependencies

**Fail criteria:** Missing `fiftyone.yml`, missing `name`, or entry file doesn't exist → stop eval, report as broken.

### Phase 2: Security & Trust

**This is the most critical phase.** FiftyOne plugins run with full OS-level permissions — no sandbox. A plugin can read files, make network calls, run commands, and access environment variables. Check for misuse.

**2.1 Filesystem access — scan all Python files for:**
```python
# CRITICAL: Look for these patterns
open()           # File read/write — is it within the plugin directory or dataset paths?
os.path          # Path manipulation — is it accessing user home, SSH keys, credentials?
shutil           # File copying — where is it copying to?
pathlib          # Same as os.path
glob.glob        # File discovery — what directories is it scanning?
```

**Acceptable:** Reading/writing within FiftyOne dataset directories, plugin directory, temp files.
**Suspicious:** Reading `~/.ssh/`, `~/.aws/`, `~/.config/`, `/etc/`, or any path outside FiftyOne scope. On Windows, equivalent suspicious paths are `%USERPROFILE%\.ssh\`, `%USERPROFILE%\.aws\`, `%APPDATA%\`, and `C:\Windows\System32\`. Credential access must go through the `fiftyone.yml` `secrets:` mechanism (see 2.4) — direct filesystem reads of credential files bypass the user consent contract even if the secret is declared in the manifest.
**Critical:** Writing to system directories or reading credential files. Plugin properly leverages and handles environment variables

**2.2 Network access — scan for:**
```python
# CRITICAL: Look for these patterns
requests         # HTTP calls — to where?
urllib           # Same
http.client      # Same
socket           # Raw sockets — almost never legitimate for a plugin
aiohttp          # Async HTTP
httpx            # Same
```

**Acceptable:** Calls to documented APIs and services the plugin integrates with (e.g., annotation backend, model API) that match declared secrets.
**Suspicious:** Calls to unknown external endpoints, IP addresses, or domains not directly related to the plugin's stated purpose or related services.
**Critical:** Data being sent to external servers that includes dataset content, user info, or environment variables.

**2.3 Command execution — scan for:**
```python
# CRITICAL: These should rarely appear in plugins
subprocess       # Shell commands — what commands?
os.system        # Same
os.popen         # Same
exec()           # Dynamic code execution
eval()           # Same
__import__       # Dynamic imports
importlib        # Another pattern for dynamic imports
```

**Acceptable:** Running specific, documented external tools (e.g., `ffmpeg` for video processing in an I/O plugin).
**Suspicious:** Running arbitrary shell commands, especially quietly or in an obfuscated manner, with user-provided input.
**Critical:** `exec()` or `eval()` on any string, bytes, or arbitrary code not directly included in plaintext with the plugin.

**2.4 Environment variable access:**
```python
# Check for broad env var access
os.environ       # Reading ALL env vars — plugins should only use declared secrets
os.getenv        # Reading specific env vars — which ones?
```

**Acceptable:** Reading env vars that match the plugin's declared `secrets` in `fiftyone.yml`. This is the only legitimate credential access path — even if a secret is declared, reading the same credential from disk (e.g., `~/.aws/`) is still suspicious (see 2.1).
**Suspicious:** Reading env vars not declared as secrets (e.g., `AWS_SECRET_ACCESS_KEY`, `GITHUB_TOKEN`).
**Critical:** Iterating over `os.environ` to dump all environment variables or sending environment variable contents to locations not related to service authentication.

**2.5 Data exfiltration patterns — check for combinations:**
- Reads a file + makes a network call in the same function → suspicious
- Reads env vars + makes a network call → suspicious
- Accesses dataset samples and/or writes to a non-FiftyOne path → suspicious
- Patterns allowing arbitrary path traversal or relative path access
- Base64 encoding + network call → highly suspicious

**2.6 Dependency supply chain:**
- Check `requirements.txt` for known malicious packages
- Check for typos (e.g., `reqeusts` instead of `requests`)
- Flag packages with very low download counts or no source repository

**Fail criteria:** Any critical finding in 2.1–2.5 → plugin is unsafe, stop eval and report immediately.

### Phase 3: Registration & MCP Readiness

Load the plugin and verify it integrates correctly with FiftyOne.

**3.1 Registration:**
```python
list_plugins(enabled=True)  # Plugin appears?
list_operators()            # All declared operators visible?
```
- `register()` function completes without errors
- All operators from manifest appear in registry
- No name collisions with builtin `@voxel51/*` operators

**3.2 MCP tool exposure: (Optional)** 
- Operators that should be LLM-accessible are exposed as MCP tools
- Internal/helper operators are correctly marked as `unlisted=True`
- Each exposed tool has a description that would help an LLM choose it

**3.3 Startup behavior:**
- If any operator has `on_startup=True`, verify it executes cleanly
- Startup operators should be fast (< 1 second) — they block App initialization
- Side effects must be limited to lightweight initialization (loading config, registering state) — no data mutations, network calls, or heavy computation at startup

### Phase 4: Schema & Contract Quality

Check that every operator has well-defined inputs and outputs.

**4.1 Input schemas:**
```python
# For each public operator
get_operator_schema(operator_uri="@org/operator-name")
```
- `resolve_input()` returns a valid property tree
- Required fields are marked as `required=True`
- Fields have descriptive labels (not "field1", "param_a")
- Enums have reasonable option lists
- Dynamic operators (`dynamic=True`) adapt their schemas correctly

**4.2 Output schemas:**
- `resolve_output()` exists for operators that return meaningful data
- Output types match what `execute()` actually returns — where type annotations are absent, check that fields declared in `resolve_output()` are referenced in `execute()` and vice versa (flag mismatches as warnings, not failures)

**4.3 Error handling:**
- Empty params → clean validation error, not a crash
- Required parameters that are empty → clean validation error with a clear message shown to the user, no crash or unhandled exception
- Wrong types (`TypeError`) or invalid values (`ValueError`) → clean error message shown to the user, no unhandled exception
- Missing required fields → clear indication of what's missing

### Phase 5: Risk Classification

Check that operators are correctly classified by danger level.

**5.1 Operator severity declaration:**
- Check if operators declare a `severity` in their `OperatorConfig` (e.g., `foo.OperatorConfig(..., severity=foo.Severity.DANGEROUS)`)
- FiftyOne supports severity levels that control how operators are presented and confirmed in the App
- If no `severity` is set on any operator, note this as an observation — the plugin does not use the built-in severity system. This means the App won't show automatic risk indicators or confirmation prompts based on severity.

**5.2 Destructive operations:**
- Any operator that deletes samples, fields, datasets, or files should have `allow_delegated_execution=True` or require confirmation
- Check for: `dataset.delete_samples()`, `dataset.delete_field()`, `sample.delete()`, `shutil.rmtree()`, `os.remove()`
- Destructive operations MUST NOT be auto-executable without user confirmation

**5.2 Data modification:**
- Operators that modify sample fields, add labels, or change metadata should be clearly labeled
- Users must be notified of all expected changes before execution

**5.3 External side effects:**
- Operators that make API calls, send data externally, or trigger external systems should require explicit user consent
- Check if the operator description warns the user about external calls

**5.4 Cost implications:**
- Operators that trigger paid API calls (LLM inference, cloud compute) should document expected cost
- `allow_delegated_execution` should be true for long-running paid operations

### Phase 6: Code Quality

Check the plugin follows FiftyOne conventions.

**6.1 FiftyOne patterns:**
- Uses `ctx.target_view()` for processing (not raw `ctx.dataset`)
- Uses `ctx.store()` with namespaced keys (not generic "data" or "config")
- Operators performing compute-heavy work (model inference, brain ops, bulk field writes) should expose `allow_delegated_execution=True` so users can opt in — delegation should be available but not forced
- Operators that iterate over dataset samples should use `execute_as_generator` for memory efficiency and progress reporting
- Progress reporting for long operations

**6.2 Code style:**
- Optionally follows `fiftyone-code-style` conventions (imports, naming, aliases) — flag deviations as suggestions, not failures
- No hardcoded dataset names, file paths, or model names
- Dynamic discovery via `list_operators()`, `get_operator_schema()` instead of assumptions

**6.3 ExecutionStore usage:**
- Store keys are scoped (include plugin name and dataset ID)
- TTL set on cached data (not storing forever)
- No reading other plugins' store keys

### Phase 7: Agent Discoverability (Optional)

Check that an LLM agent can effectively use this plugin's tools. Flag all findings in this phase as suggestions, not failures — agent discoverability is a quality improvement, not a requirement.

**7.1 Tool naming:**
- All operator names, that are **not** unlisted `unlisted=True`, describe what they do (`compute_embeddings`, not `run_pipeline_v2`)
- Names are distinct from builtin operators (no confusion with `@voxel51/*` tools)

**7.2 Tool descriptions:**
- Each operator's `label` and `description` clearly explain when to use it
- A non-expert reading just the description could decide whether this tool fits their task

**7.3 Ambiguity check:**
- No two operators in the plugin have overlapping descriptions
- The distinction between similar operators is clear (e.g., "import_images" vs "import_labels")

**7.4 Companion skills:**
- Check if the plugin declares `skills:` in `fiftyone.yml` (verified in Phase 1.2)
- If skills exist, verify each skill provides a clear step-by-step workflow that reference the plugin's operators
- If no skills exist and the plugin has >3 operators that are typically used in sequence, recommend creating one — it would improve agent success rate significantly

## Report Format

Generate the report in this structure:

```markdown
# Plugin Eval Report: {plugin_name}

## Summary
- **Security Gate:** {PASS | WARN | FAIL} — if FAIL, stop here: DO NOT INSTALL
- **Overall Score:** {score}/100 (N/A if Security = FAIL; capped at 70 if Security = WARN)
- **Quality:** {score}/100
- **Agent Readiness:** {score}/100
- **Critical Issues:** {count}
- **Warnings:** {count}

## Security Assessment
{List each finding with severity, file, line, description, and fix}

## Critical Issues
{Findings that must be fixed before the plugin is safe to use}

## Warnings
{Findings that should be fixed but don't block usage}

## Passed Checks
{What the plugin does well — give credit where due}

## Recommendations
{Ordered list: most impactful fix first, with specific instructions}

## Component Scores
| Area | Score (0–100) | Key Finding |
|------|--------------|-------------|
| Manifest & Structure | {score}/100 | {one-line summary} |
| Security & Trust | {score}/100 | {one-line summary} |
| Registration & MCP | {score}/100 | {one-line summary} |
| Schema & Contract | {score}/100 | {one-line summary} |
| Risk Classification | {score}/100 | {one-line summary} |
| Code Quality | {score}/100 | {one-line summary} |
| Agent Discoverability | {score}/100 | {one-line summary} |
```

## Scoring

**Security is a hard gate — evaluate before scoring:**
- **FAIL** (1+ critical findings) → stop evaluation, overall score = N/A, verdict = DO NOT INSTALL
- **WARN** (1+ suspicious findings) → proceed with scoring, cap overall score at 70/100
- **PASS** (0 critical findings) → proceed with full scoring

**Overall = weighted average of remaining components (only if Security = PASS or WARN):**
- Schema & Contract: 25% (correctness matters)
- Agent Discoverability: 20% (usability for LLM agents)
- Risk Classification: 20% (user safety)
- Code Quality: 15% (maintainability)
- Registration & MCP: 10% (basic functionality)
- Manifest & Structure: 10% (basic hygiene)

## Quick Reference

### Common Security Patterns to Flag

| Pattern | Severity | Why |
|---------|----------|-----|
| `os.environ` iteration | Critical | Dumps all env vars including secrets |
| `subprocess` + user input | Critical | Command injection risk |
| `exec()` / `eval()` | Critical | Arbitrary code execution |
| `requests.post()` + dataset data | Critical | Data exfiltration |
| `open("/etc/...")` / `open("C:\\Windows\\...")` | Critical | System file access |
| `open("~/.ssh/...")` / `open("%USERPROFILE%\\.ssh\\...")` | Critical | Credential theft |
| `socket.connect()` | Suspicious | Raw network access |
| `os.getenv("AWS_*")` undeclared | Suspicious | Accessing secrets not in manifest |
| `shutil.copytree()` to external path | Suspicious | Data copying |
| No `resolve_input()` | Warning | Unvalidated inputs |
| Generic store keys | Warning | Cross-plugin data leaks |

### Score Interpretation

| Score | Meaning |
|-------|---------|
| 90-100 | Production-ready, well-built plugin |
| 70-89 | Good plugin with minor improvements needed |
| 50-69 | Usable but has significant gaps |
| 30-49 | Needs major work before deployment |
| 0-29 | Unsafe or fundamentally broken |

## Troubleshooting

> For general FiftyOne environment issues (database errors, App won't open, missing plugins, MongoDB failures), use the `fiftyone-troubleshoot` skill — it covers issues outside the scope of plugin evaluation.

**Plugin won't load for evaluation:**
- Check `fiftyone.yml` exists and has valid YAML syntax
- Check Python entry file has no import errors
- Try: `python -c "import fiftyone; fiftyone.plugins.list_plugins()"`

**Can't check MCP exposure:**
- Verify FiftyOne MCP server is available
- Check: `fiftyone-mcp` binary exists in PATH

**Security scan finds too many false positives:**
- Check if flagged patterns are inside test files (tests/ directory) — lower severity
- Check if network calls match declared secrets — likely legitimate
- Use context: an I/O plugin making HTTP calls to a documented API is expected

## Resources

- [FiftyOne Plugin Development Guide](https://docs.voxel51.com/plugins/developing_plugins.html)
- [Anthropic's Framework for Safe Agents](https://www.anthropic.com/news/our-framework-for-developing-safe-and-trustworthy-agents)
- [FiftyOne Plugins Repository](https://github.com/voxel51/fiftyone-plugins)
- [fiftyone-develop-plugin skill](../fiftyone-develop-plugin/SKILL.md) — for fixing code quality issues
- [fiftyone-code-style skill](../fiftyone-code-style/SKILL.md) — for fixing style issues

