This is the single source of truth for all AI coding agents (Claude, Gemini, others) working on ModelAudit, a security scanner for AI/ML model files. Follow it exactly and keep instructions concise through progressive disclosure—share only the minimum needed context and iterate.
- Agents start with zero context; use this file to bootstrap each session with the essentials: what (stack/project map), why (security-focused scanner), and how (workflow + validation below).
- Prefer pointers over payloads: read the specific docs in
docs/agents/when needed instead of inlining here. - Keep instructions universal and minimal; lean on deterministic tools (ruff, mypy, pytest, prettier) rather than embedding style rules.
- When unsure, ask or fetch targeted context instead of expanding instructions.
- Security first: Never weaken detections or bypass safeguards.
- Match the codebase: Follow existing patterns, architecture, and naming; never add dependencies without approval.
- Progressive disclosure: Be concise, reveal details as needed, and prefer short, scoped messages.
- Iterative refinement: Share a plan for non-trivial work, execute incrementally, and verify after each change.
- Ask when unclear: Confirm scope before risky or ambiguous actions.
- Proactive completion: Provide tests and follow-up steps without waiting to be asked.
# Setup
uv sync --extra all-ci
# Pre-commit workflow (MUST run before every commit)
uv run ruff format modelaudit/ packages/modelaudit-picklescan/src packages/modelaudit-picklescan/tests tests/
uv run ruff check --fix modelaudit/ packages/modelaudit-picklescan/src packages/modelaudit-picklescan/tests tests/
uv run mypy modelaudit/ packages/modelaudit-picklescan/src packages/modelaudit-picklescan/tests tests/
PROMPTFOO_DISABLE_TELEMETRY=1 uv run pytest -n auto -m "not slow and not integration" --maxfail=1- Understand: Read nearby code, tests, and docs (
docs/agents/*.md) before editing. - Plan: For anything non-trivial, present a short multi-step plan; refine iteratively.
- Implement: Preserve security focus, follow
BaseScannerpatterns (seedocs/agents/architecture.md), handle missing deps gracefully, and updateSCANNER_REGISTRYwhen adding scanners. - Verify: Run the validation commands above. Format/linters must be clean. Use targeted
pytestwhen appropriate, and type-check modified tests as part of the normalmypy modelaudit/ packages/modelaudit-picklescan/src packages/modelaudit-picklescan/tests tests/pass. - Report: Summarize changes with file references and note residual risks or follow-ups.
NEVER commit or push directly to main. All changes must go through pull requests.
# Start clean
git fetch origin main
git checkout main
git merge --no-edit origin/main
# Work on a branch (REQUIRED - never commit to main)
git checkout -b feat/your-feature-name # or fix/, chore/, test/
# Commit (conventional)
git commit -m "feat: add scanner for XYZ format
Description here."
# PR (after validation) - ALL changes go through PRs
git push -u origin feat/your-feature-name
gh pr create --title "feat: descriptive title" --body "Brief description"- Use non-interactive flags (
--no-edit,-m). One command per invocation; avoid long&&chains. - When updating an existing PR, confirm the actual head branch with
gh pr view <number> --json headRefNameand sync that branch before editing or pushing. - If
.git/index.lockexists and no git process is running, remove the lock file. - Add only intended paths; avoid committing artifacts. Prefer
gh run rerun <run-id>over force-pushing to rerun CI. - Keep CHANGELOG entries in
[Unreleased]when adding user-visible changes (Keep a Changelog format).
uv run ruff check modelaudit/ packages/modelaudit-picklescan/src packages/modelaudit-picklescan/tests tests/ # Lint (no errors)
uv run ruff format --check modelaudit/ packages/modelaudit-picklescan/src packages/modelaudit-picklescan/tests tests/ # Format (no changes)
uv run mypy modelaudit/ packages/modelaudit-picklescan/src packages/modelaudit-picklescan/tests tests/ # Types (no errors)
PROMPTFOO_DISABLE_TELEMETRY=1 uv run pytest -n auto -m "not slow and not integration" --maxfail=1| Issue | Fix |
|---|---|
| Import organization | uv run ruff check --fix --select I modelaudit/ packages/modelaudit-picklescan/src packages/modelaudit-picklescan/tests tests/ |
| Format issues | uv run ruff format modelaudit/ packages/modelaudit-picklescan/src packages/modelaudit-picklescan/tests tests/ |
| Type errors | Fix manually, re-run mypy |
| Test failures | Check output, fix issues, re-run tests |
- ModelAudit supports both NumPy 1.x (Python 3.10) and 2.x (Python 3.11+) via environment markers in
pyproject.toml. Seedocs/agents/dependencies.mdfor the full strategy, vendored proto guide, and rules for adding dependencies. - When adding dependencies, check compatibility with both NumPy versions and add environment markers if needed.
- Python: 3.10–3.13 supported. Classes PascalCase, functions/vars snake_case, constants UPPER_SNAKE_CASE, always type hints.
- Comments: Use sparingly to explain intent, not mechanics.
- Docs/Markdown: Keep concise; when formatting markdown/json/yaml, use
npm ci --ignore-scripts && npx prettier --write "**/*.{md,yaml,yml,json}"if instructed or if formatting drifts. - Dependencies: Do not add new packages without explicit approval and updating
pyproject.toml/locks. - Performance & safety: Prefer safe defaults; avoid destructive commands.
- Preserve or strengthen detections; test both benign and malicious samples.
- Follow existing scanner patterns and update registries, CLI wiring, and docs as needed.
- Add comprehensive tests, including edge cases and regression coverage.
- Ensure compatibility across Python 3.10–3.13 and handle missing optional deps gracefully.
- Apply the test-style rules below to all new or modified tests, not just CVE coverage.
- Use typed pytest tests: add
-> None, annotate fixtures liketmp_path: Path/monkeypatch: pytest.MonkeyPatch, and preferpathliboveros.path. - Keep fixtures deterministic and self-contained under
tmp_path; never rely on host paths or global temp filenames. - If a new regression test must run on reduced CI lanes, add the file to
allowed_test_filesintests/conftest.py. - Match local validation to the CI lane that will exercise the change when possible; if optional dependencies or Python-version gates prevent that, call it out explicitly in the PR.
- Disable telemetry for local validation unless the task explicitly tests telemetry behavior: prefix pytest runs with
PROMPTFOO_DISABLE_TELEMETRY=1(orNO_ANALYTICS=1). Requests to contacta.promptfoo.appduring normal unit, scanner, or integration-excluded test runs are ok and expected for this project; approve them when the test runner requests access. Telemetry tests should still mock the transport or make network intent explicit before running. - For file routing, prefiltering, or archive-triage changes, add at least one malicious positive regression and one benign near-match negative regression.
- Reuse shared fixture helpers for container formats. For PyTorch ZIP tests, prefer
tests.helpers.create_mock_pytorch_zip; if you hand-roll a ZIP-backed.pt/.pkl, include minimal PyTorch markers likeversionorbyteorderso routing matches production. - Prefer trusted file structure or bounded content sniffing over suffix-only routing. Extension checks are a fallback, not a source of truth.
- When a scan intentionally fails closed for coverage or safety reasons, make the behavior operationally explicit and test the message plus the relevant
success, exit-code, and cache semantics.
When adding CVE detections to existing scanners, follow these rules distilled from recurring review feedback across 13 CVE implementations.
- Doc/comment guards: Use majority-line analysis (>50% doc lines via
_is_primarily_documentation()), not substring checks —"#" in contentis trivially bypassable by embedding a comment token in a payload. STACK_GLOBALhandling: These opcodes havearg=Nonein pickletools; reconstructmodule.classby walking backwards to find precedingSHORT_BINUNICODE/BINUNICODEops.- Dict short-circuit scope: Track which op produces the
SETITEMtarget — an unrelatedEMPTY_DICTin the lookback window must not suppress detection of aSETITEMtargeting aREDUCE/NEWOBJresult. - Version comparison: Handle PEP 440 prerelease tags (
a,b,rc,dev) — for a framework/library version gate, a prerelease like2.10.0a0is still vulnerable, not the fix. - Bounded reads: Cap archive member reads for metadata validation (10 MB) to prevent memory spikes on large pickles.
- Pattern registration: New CVE pattern lists must be added to
validate_patterns()insuspicious_symbols.py.
- Always include
cve_id,cvss,cwe,description,remediationin thedetailsdict. - Include context fields (e.g.,
layer_namefor Keras,installed_pytorch_versionfor PyTorch) — keep consistent across scanner variants (H5 vs ZIP). - Use
except Exception(notexcept ImportError) when importing frameworks for version checks, since mocked/broken modules may raise other errors.
- Assert the actual signal — never
assert result is not Nonealone; verify specific check names, issue messages, or detail fields. - For "fixed version" tests: also verify the vulnerable version test produces a failed check (prevents silent regression).
- Test bypass prevention: verify that embedding a single comment token in a malicious payload does NOT suppress detection.
- Deterministic fixtures only — never reference host paths like
/etc/passwd; create all targets undertmp_path. - Type hints:
-> Noneon all test methods,tmp_path: Path/monkeypatch: pytest.MonkeyPatchon parameters. - Use pathlib (
tmp_path / "file.ext") instead ofos.path.join.
- Add new test files to
allowed_test_filesintests/conftest.py(Python 3.10/3.12/3.13 CI allowlist). - Add CVE explanation functions to
modelaudit/config/explanations.py. - Add CHANGELOG entry under the existing
[Unreleased]section (never create a second one).
For the full multi-file workflow, see docs/agents/new-scanner-quickstart.md § "Adding CVE Detections to Existing Scanners".
modelaudit/
├── packages/modelaudit-picklescan/ # Standalone pickle scanner package
├── modelaudit/ # Main package
│ ├── analysis/ # Semantic and integrated analysis
│ ├── auth/ # API authentication and config
│ ├── cache/ # Scan result caching
│ ├── config/ # Blocklists and configuration
│ ├── detectors/ # Security detectors (secrets, JIT, network)
│ ├── integrations/ # SARIF, JFrog, license checking
│ ├── progress/ # Progress tracking subsystem
│ ├── protos/ # Vendored TensorFlow protobuf stubs
│ ├── scanners/ # Scanner implementations (40+)
│ ├── utils/ # File detection, helpers, streaming
│ ├── whitelists/ # HuggingFace/model whitelists
│ ├── cli.py # CLI interface
│ ├── core.py # Core scanning orchestration
│ ├── models.py # Pydantic result models
│ └── telemetry.py # Anonymous usage telemetry
├── tests/ # Test suite
├── docs/agents/ # Agent documentation
├── docs/maintainers/ # Release, CVE, dependency docs
├── docs/user/ # User-facing guides
└── CHANGELOG.md # Keep a Changelog formatKey docs: docs/agents/architecture.md, docs/agents/dependencies.md, docs/agents/release-process.md, docs/agents/new-scanner-quickstart.md.
The README is published to PyPI and visible to the public. Keep it user-facing: product overview, installation, usage examples, supported formats, CLI options, output formats, and troubleshooting. Describe WHAT we analyze (formats/frameworks), not HOW (detection mechanisms). Implementation details belong in source code and contributor docs.
- Do: Keep responses short; surface only relevant details; prefer targeted tests; propose clear next steps; cite file paths when reporting.
- Do: Use iterative refinement—small changes, verify, then proceed.
- Do: Always use feature branches and PRs for all changes.
- Don't: Commit or push directly to
main—always use a PR. - Don't: Introduce new dependencies, weaken security checks, or bypass validation.
- Don't: Leave formatting/lint failures or unaddressed test regressions.
0: No security issues1: Security issues detected2: Scan errors
- Claude / Gemini / others: Follow this guide as canonical. Apply progressive disclosure, confirm ambiguities, and prioritize security.