mirror of
https://github.com/github/awesome-copilot.git
synced 2026-08-01 23:12:29 +00:00
Add attester package-verification instruction and import-check hook
The instruction tells Copilot to verify PyPI/npm package and symbol names against the attester.dev existence oracle (free keyless tier, 25 calls/day per client IP) before installing or importing, catching hallucinated dependency names (5.2-21.7% of LLM suggestions per USENIX Security 2025). The preToolUse hook enforces the same check on code the agent writes: stdlib-only script, blocks only on a confident oracle negative, fails open on quota or network trouble, caches answers on disk. README tables regenerated with npm start.
This commit is contained in:
@@ -31,6 +31,7 @@ See [CONTRIBUTING.md](../CONTRIBUTING.md#adding-hooks) for guidelines on how to
|
||||
|
||||
| Name | Description | Events | Bundled Assets |
|
||||
| ---- | ----------- | ------ | -------------- |
|
||||
| [Attester Import Check](../hooks/attester-import-check/README.md) | Verifies PyPI and npm package names against the attester.dev existence oracle before the Copilot coding agent writes them into code, blocking hallucinated dependencies | preToolUse | `check-imports.py`<br />`hooks.json` |
|
||||
| [Dependency License Checker](../hooks/dependency-license-checker/README.md) | Scans newly added dependencies for license compliance (GPL, AGPL, etc.) at session end | sessionEnd | `check-licenses.sh`<br />`hooks.json` |
|
||||
| [Fix Broken Links](../hooks/fix-broken-links/README.md) | Checks changed web files for broken hyperlinks and SEO anchor issues after each Copilot tool use. | postToolUse | `hooks.json`<br />`link-fix.ps1`<br />`link-fix.sh` |
|
||||
| [Governance Audit](../hooks/governance-audit/README.md) | Scans Copilot agent prompts for threat signals and logs governance events | sessionStart, sessionEnd, userPromptSubmitted | `audit-prompt.sh`<br />`audit-session-end.sh`<br />`audit-session-start.sh`<br />`hooks.json` |
|
||||
|
||||
@@ -203,6 +203,7 @@ See [CONTRIBUTING.md](../CONTRIBUTING.md#adding-instructions) for guidelines on
|
||||
| [Upgrading from .NET MAUI 9 to .NET MAUI 10](../instructions/dotnet-maui-9-to-dotnet-maui-10-upgrade.instructions.md)<br />[](https://aka.ms/awesome-copilot/install/instructions?url=vscode%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fdotnet-maui-9-to-dotnet-maui-10-upgrade.instructions.md)<br />[](https://aka.ms/awesome-copilot/install/instructions?url=vscode-insiders%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fdotnet-maui-9-to-dotnet-maui-10-upgrade.instructions.md) | Instructions for upgrading .NET MAUI applications from version 9 to version 10, including breaking changes, deprecated APIs, and migration strategies for ListView to CollectionView. |
|
||||
| [Use Cliche Data in Documentation](../instructions/use-cliche-data-in-docs.instructions.md)<br />[](https://aka.ms/awesome-copilot/install/instructions?url=vscode%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fuse-cliche-data-in-docs.instructions.md)<br />[](https://aka.ms/awesome-copilot/install/instructions?url=vscode-insiders%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fuse-cliche-data-in-docs.instructions.md) | Ensure documentation and examples use only generic, cliche placeholder data — never real or sensitive data sourced from local scripts, configuration, task files, or prompt context. |
|
||||
| [Use Code Components in Power Pages](../instructions/pcf-power-pages.instructions.md)<br />[](https://aka.ms/awesome-copilot/install/instructions?url=vscode%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fpcf-power-pages.instructions.md)<br />[](https://aka.ms/awesome-copilot/install/instructions?url=vscode-insiders%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fpcf-power-pages.instructions.md) | Using code components in Power Pages sites |
|
||||
| [Verify packages before installing or importing](../instructions/attester-verify-packages.instructions.md)<br />[](https://aka.ms/awesome-copilot/install/instructions?url=vscode%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fattester-verify-packages.instructions.md)<br />[](https://aka.ms/awesome-copilot/install/instructions?url=vscode-insiders%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fattester-verify-packages.instructions.md) | Verify PyPI and npm package and symbol names against the attester.dev existence oracle before installing or importing, so hallucinated dependencies never reach code |
|
||||
| [Visual Studio Extension Development with Community.VisualStudio.Toolkit](../instructions/vsixtoolkit.instructions.md)<br />[](https://aka.ms/awesome-copilot/install/instructions?url=vscode%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fvsixtoolkit.instructions.md)<br />[](https://aka.ms/awesome-copilot/install/instructions?url=vscode-insiders%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fvsixtoolkit.instructions.md) | Guidelines for Visual Studio extension (VSIX) development using Community.VisualStudio.Toolkit |
|
||||
| [Vue 3 Development Instructions](../instructions/vue.instructions.md)<br />[](https://aka.ms/awesome-copilot/install/instructions?url=vscode%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fvue.instructions.md)<br />[](https://aka.ms/awesome-copilot/install/instructions?url=vscode-insiders%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fvue.instructions.md) | Comprehensive Vue 3 development standards and best practices: Composition API, `<script setup>`, the full reactivity system, compiler macros (defineModel/defineSlots/defineOptions), built-in components (Teleport/Suspense/Transition/KeepAlive), provide/inject, composables, Pinia, Vue Router, TypeScript, testing, performance, SSR, and security. |
|
||||
| [WinUI 3 / Windows App SDK](../instructions/winui3.instructions.md)<br />[](https://aka.ms/awesome-copilot/install/instructions?url=vscode%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fwinui3.instructions.md)<br />[](https://aka.ms/awesome-copilot/install/instructions?url=vscode-insiders%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fwinui3.instructions.md) | WinUI 3 and Windows App SDK coding guidelines. Prevents common UWP API misuse, enforces correct XAML namespaces, threading, windowing, and MVVM patterns for desktop Windows apps. |
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
---
|
||||
name: 'Attester Import Check'
|
||||
description: 'Verifies PyPI and npm package names against the attester.dev existence oracle before the Copilot coding agent writes them into code, blocking hallucinated dependencies'
|
||||
tags: ['security', 'supply-chain', 'preToolUse', 'dependencies']
|
||||
---
|
||||
|
||||
# Attester Import Check Hook
|
||||
|
||||
Verifies every third-party package name the Copilot coding agent is about to write into code, before the write lands. A USENIX Security 2025 study measured 5.2% to 21.7% of LLM-suggested package names as nonexistent; this hook catches those names at the door.
|
||||
|
||||
## Overview
|
||||
|
||||
On the `preToolUse` event the hook receives the tool invocation as JSON on stdin, extracts import statements from the code being introduced, and checks each package name against the attester.dev existence oracle. The oracle answers from real published artifacts (PyPI wheels, npm tarballs), so a "does not exist" answer is deterministic, not a model opinion.
|
||||
|
||||
## Behavior contract
|
||||
|
||||
- **Blocks (exit 1)** only on a confident negative from the oracle.
|
||||
- **Fails open (exit 0)** when the free daily quota is spent, the API is unreachable, or the payload is unusable. It never blocks on these.
|
||||
- Python: skips standard library modules and relative imports. JS/TS: skips relative paths, absolute paths, and node builtins; `@scope/name` handled.
|
||||
- Answers are cached at `~/.cache/attester-import-check/cache.json` (exists 30 days, negatives 1 day) so repeated edits do not burn the free quota.
|
||||
- Allowlist import names that differ from their registry package (`yaml`, `PIL`, `cv2`) via `.attester-allowlist` in the workspace root, one per line.
|
||||
|
||||
## Free quota and the paid path
|
||||
|
||||
The check uses the free keyless tier: 25 calls per day per client IP, no account or API key, reset 00:00 UTC. Over quota the hook prints "attester quota exhausted, unchecked" and allows the operation. High volume: $0.002 per package check on the paid route (x402 or prepaid credits), documented at https://attester.dev/llms.txt.
|
||||
|
||||
## Installation
|
||||
|
||||
1. Copy the hook folder to your repository:
|
||||
|
||||
```bash
|
||||
cp -r hooks/attester-import-check your-repo/hooks/
|
||||
```
|
||||
|
||||
2. Ensure the script is executable:
|
||||
|
||||
```bash
|
||||
chmod +x hooks/attester-import-check/check-imports.py
|
||||
```
|
||||
|
||||
3. Commit the hook configuration to your repository's default branch.
|
||||
|
||||
The script is Python 3.10+ standard library only. No pip install step.
|
||||
|
||||
## Configuration
|
||||
|
||||
The hook is configured in `hooks.json` to run on the `preToolUse` event:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"hooks": {
|
||||
"preToolUse": [
|
||||
{
|
||||
"type": "command",
|
||||
"bash": "hooks/attester-import-check/check-imports.py",
|
||||
"cwd": ".",
|
||||
"env": {
|
||||
"ATTESTER_MODE": "block"
|
||||
},
|
||||
"timeoutSec": 30
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
|
||||
| Variable | Values | Default | Description |
|
||||
|----------|--------|---------|-------------|
|
||||
| `ATTESTER_MODE` | `block`, `warn` | `block` | `warn` prints findings but always allows the operation |
|
||||
| `ATTESTER_BASE_URL` | URL | `https://attester.dev` | Oracle base URL |
|
||||
| `ATTESTER_IMPORT_CHECK_NO_CACHE` | `1` | unset | Skip the on-disk answer cache |
|
||||
|
||||
## Examples
|
||||
|
||||
### Hallucinated package (exit 1, blocked)
|
||||
|
||||
```bash
|
||||
echo '{"toolName":"write_file","toolInput":{"path":"main.py","content":"import requestsx_fantasy_helper"}}' | \
|
||||
python3 hooks/attester-import-check/check-imports.py
|
||||
```
|
||||
|
||||
```
|
||||
attester-import-check: 'requestsx_fantasy_helper' does not exist on PyPI (attester.dev oracle). Remove or fix the import, or add the name to .attester-allowlist if this is a false positive.
|
||||
```
|
||||
|
||||
### Real package (exit 0)
|
||||
|
||||
```bash
|
||||
echo '{"toolName":"write_file","toolInput":{"path":"main.py","content":"import requests"}}' | \
|
||||
python3 hooks/attester-import-check/check-imports.py
|
||||
```
|
||||
|
||||
### Quota exhausted (exit 0, allowed with warning)
|
||||
|
||||
```
|
||||
attester-import-check: attester quota exhausted, unchecked
|
||||
```
|
||||
|
||||
## Limitations
|
||||
|
||||
- Import names that differ from distribution names (`yaml` for PyYAML, `PIL` for Pillow) can warn falsely; allowlist them.
|
||||
- Python dynamic imports (`__import__`, `importlib.import_module`) and bundler path aliases are not resolved.
|
||||
- The hook checks names against a public registry oracle; private packages are "not found" there by design.
|
||||
|
||||
## Source project
|
||||
|
||||
The standalone version of this guard (pre-commit hook, Claude Code hook, GitHub Action) lives at https://github.com/maminihds/attester-import-check. The oracle behind it is https://attester.dev.
|
||||
Executable
+240
@@ -0,0 +1,240 @@
|
||||
#!/usr/bin/env python3
|
||||
"""attester-import-check hook for GitHub Copilot coding agent (preToolUse).
|
||||
|
||||
Reads the tool invocation as JSON on stdin ({"toolName", "toolInput"}),
|
||||
extracts package imports from the code being introduced, and checks each
|
||||
name against the attester.dev existence oracle (free keyless tier, 25
|
||||
calls/day per client IP). Exits 1 to block on a confident "does not exist".
|
||||
Quota exhaustion, offline, and payload problems fail open (exit 0): a guard
|
||||
that blocks the wrong operation is worse than one that misses one.
|
||||
|
||||
Stdlib only. Answers are cached at ~/.cache/attester-import-check/cache.json
|
||||
(exists 30 days, negatives 1 day) so repeated edits do not burn quota.
|
||||
|
||||
Env:
|
||||
ATTESTER_MODE=block|warn default block (warn never blocks)
|
||||
ATTESTER_BASE_URL default https://attester.dev
|
||||
ATTESTER_IMPORT_CHECK_NO_CACHE=1 skip the answer cache
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import ast
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
|
||||
BASE_URL = os.environ.get("ATTESTER_BASE_URL", "https://attester.dev").rstrip("/")
|
||||
CACHE_PATH = Path.home() / ".cache" / "attester-import-check" / "cache.json"
|
||||
TTL_POSITIVE_S = 30 * 24 * 3600
|
||||
TTL_NEGATIVE_S = 24 * 3600
|
||||
TIMEOUT_S = 10.0
|
||||
|
||||
PY_EXTS = {".py", ".pyi"}
|
||||
JS_EXTS = {".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs", ".mts", ".cts"}
|
||||
NODE_BUILTINS = frozenset(
|
||||
"""
|
||||
assert async_hooks buffer child_process cluster console constants crypto
|
||||
dgram diagnostics_channel dns domain events fs http http2 https inspector
|
||||
module net os path perf_hooks process punycode querystring readline repl
|
||||
sea sqlite stream string_decoder sys test timers tls trace_events tty url
|
||||
util v8 vm wasi worker_threads zlib
|
||||
""".split()
|
||||
)
|
||||
PATH_KEYS = {"path", "filePath", "file_path", "filename", "file"}
|
||||
|
||||
_JS_SPEC_RE = re.compile(
|
||||
r"""
|
||||
\bfrom\s*['"]([^'"]+)['"]
|
||||
| \bimport\s*['"]([^'"]+)['"]
|
||||
| \brequire\(\s*['"]([^'"]+)['"]\s*\)
|
||||
| \bimport\(\s*['"]([^'"]+)['"]\s*\)
|
||||
""",
|
||||
re.VERBOSE,
|
||||
)
|
||||
|
||||
|
||||
def extract_python(source: str) -> set[str]:
|
||||
try:
|
||||
tree = ast.parse(source)
|
||||
except (SyntaxError, ValueError):
|
||||
return set()
|
||||
names = set()
|
||||
for node in ast.walk(tree):
|
||||
if isinstance(node, ast.Import):
|
||||
names.update(a.name.split(".")[0] for a in node.names)
|
||||
elif isinstance(node, ast.ImportFrom):
|
||||
if not node.level and node.module:
|
||||
names.add(node.module.split(".")[0])
|
||||
stdlib = sys.stdlib_module_names
|
||||
return {n for n in names if n not in stdlib}
|
||||
|
||||
|
||||
def js_package(spec: str) -> str | None:
|
||||
if not spec or spec.startswith((".", "/")) or spec.startswith("node:"):
|
||||
return None
|
||||
parts = spec.split("/")
|
||||
name = f"{parts[0]}/{parts[1]}" if spec.startswith("@") and len(parts) > 1 and parts[1] else parts[0]
|
||||
return None if name in NODE_BUILTINS else name
|
||||
|
||||
|
||||
def extract_js(source: str) -> set[str]:
|
||||
names = set()
|
||||
for match in _JS_SPEC_RE.finditer(source):
|
||||
name = js_package(next(g for g in match.groups() if g is not None))
|
||||
if name:
|
||||
names.add(name)
|
||||
return names
|
||||
|
||||
|
||||
def walk_strings(node):
|
||||
"""Yield (key, value) for every string in a nested JSON value."""
|
||||
if isinstance(node, dict):
|
||||
for key, value in node.items():
|
||||
yield from walk_strings(value) if not isinstance(value, str) else [(key, value)]
|
||||
elif isinstance(node, list):
|
||||
for item in node:
|
||||
yield from walk_strings(item)
|
||||
|
||||
|
||||
def load_cache() -> dict:
|
||||
if os.environ.get("ATTESTER_IMPORT_CHECK_NO_CACHE"):
|
||||
return {}
|
||||
try:
|
||||
return json.loads(CACHE_PATH.read_text())
|
||||
except (OSError, ValueError):
|
||||
return {}
|
||||
|
||||
|
||||
def save_cache(cache: dict) -> None:
|
||||
if os.environ.get("ATTESTER_IMPORT_CHECK_NO_CACHE"):
|
||||
return
|
||||
try:
|
||||
CACHE_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||
CACHE_PATH.write_text(json.dumps(cache))
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def oracle(package: str, ecosystem: str, cache: dict):
|
||||
"""True/False answer, or None when unchecked (offline). Raises SystemExit-free
|
||||
sentinel string 'quota' is returned via the cache-neutral marker below."""
|
||||
key = f"{ecosystem}:{package}"
|
||||
entry = cache.get(key)
|
||||
now = time.time()
|
||||
if entry is not None:
|
||||
ttl = TTL_POSITIVE_S if entry.get("exists") else TTL_NEGATIVE_S
|
||||
if now - entry.get("ts", 0) < ttl:
|
||||
return entry.get("exists"), entry.get("adjacent_to") or []
|
||||
body = json.dumps({"ecosystem": ecosystem, "name": package}).encode()
|
||||
req = urllib.request.Request(
|
||||
f"{BASE_URL}/demo/v1/package/exists",
|
||||
data=body,
|
||||
headers={"Content-Type": "application/json"},
|
||||
method="POST",
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=TIMEOUT_S) as resp:
|
||||
info = json.loads(resp.read())
|
||||
except urllib.error.HTTPError as exc:
|
||||
if exc.code == 429:
|
||||
return "quota", []
|
||||
return None, []
|
||||
except Exception:
|
||||
return None, []
|
||||
if "exists" not in info:
|
||||
return None, []
|
||||
cache[key] = {
|
||||
"exists": bool(info["exists"]),
|
||||
"adjacent_to": info.get("adjacent_to") or [],
|
||||
"ts": now,
|
||||
}
|
||||
save_cache(cache)
|
||||
return bool(info["exists"]), info.get("adjacent_to") or []
|
||||
|
||||
|
||||
def load_allowlist() -> set[str]:
|
||||
path = Path.cwd() / ".attester-allowlist"
|
||||
if not path.is_file():
|
||||
return set()
|
||||
return {
|
||||
line.strip()
|
||||
for line in path.read_text(errors="replace").splitlines()
|
||||
if line.strip() and not line.strip().startswith("#")
|
||||
}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
try:
|
||||
payload = json.load(sys.stdin)
|
||||
except (ValueError, OSError):
|
||||
return 0
|
||||
tool_input = payload.get("toolInput") or payload.get("tool_input") or {}
|
||||
|
||||
filename = ""
|
||||
code_chunks: list[str] = []
|
||||
for key, value in walk_strings(tool_input):
|
||||
if not filename and key in PATH_KEYS and "/" in value or (
|
||||
not filename and key in PATH_KEYS and "." in value
|
||||
):
|
||||
filename = value
|
||||
if len(value) > 15 and ("import" in value or "require(" in value):
|
||||
code_chunks.append(value)
|
||||
if not code_chunks:
|
||||
return 0
|
||||
|
||||
ext = Path(filename).suffix.lower()
|
||||
candidates: dict[str, str] = {} # package -> ecosystem
|
||||
if ext in JS_EXTS:
|
||||
for chunk in code_chunks:
|
||||
for name in extract_js(chunk):
|
||||
candidates.setdefault(name, "npm")
|
||||
elif ext in PY_EXTS or not ext:
|
||||
for chunk in code_chunks:
|
||||
for name in extract_python(chunk):
|
||||
candidates.setdefault(name, "pypi")
|
||||
if not ext: # unknown file type: also try JS-style imports
|
||||
for chunk in code_chunks:
|
||||
for name in extract_js(chunk):
|
||||
candidates.setdefault(name, "npm")
|
||||
if not candidates:
|
||||
return 0
|
||||
|
||||
allowlist = load_allowlist()
|
||||
cache = load_cache()
|
||||
findings: list[tuple[str, str, list[str]]] = []
|
||||
warn_only = os.environ.get("ATTESTER_MODE", "block").lower() == "warn"
|
||||
try:
|
||||
for package in sorted(candidates):
|
||||
if package in allowlist:
|
||||
continue
|
||||
answer, adjacent = oracle(package, candidates[package], cache)
|
||||
if answer == "quota":
|
||||
print(
|
||||
"attester-import-check: attester quota exhausted, unchecked",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 0
|
||||
if answer is False:
|
||||
findings.append((package, candidates[package], adjacent))
|
||||
except Exception:
|
||||
return 0
|
||||
|
||||
for package, ecosystem, adjacent in findings:
|
||||
registry = "PyPI" if ecosystem == "pypi" else "npm"
|
||||
msg = f"attester-import-check: '{package}' does not exist on {registry} (attester.dev oracle)."
|
||||
if adjacent:
|
||||
msg += f" Closest real name: {', '.join(adjacent)}."
|
||||
msg += " Remove or fix the import, or add the name to .attester-allowlist if this is a false positive."
|
||||
print(msg, file=sys.stderr)
|
||||
if findings and not warn_only:
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"version": 1,
|
||||
"hooks": {
|
||||
"preToolUse": [
|
||||
{
|
||||
"type": "command",
|
||||
"bash": "hooks/attester-import-check/check-imports.py",
|
||||
"cwd": ".",
|
||||
"env": {
|
||||
"ATTESTER_MODE": "block"
|
||||
},
|
||||
"timeoutSec": 30
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
description: 'Verify PyPI and npm package and symbol names against the attester.dev existence oracle before installing or importing, so hallucinated dependencies never reach code'
|
||||
applyTo: '**'
|
||||
---
|
||||
|
||||
# Verify packages before installing or importing
|
||||
|
||||
Use the attester.dev existence oracle before adding any third-party dependency or calling a library symbol you cannot confirm exists. The oracle answers from real published artifacts (PyPI wheels, npm tarballs), not from model memory.
|
||||
|
||||
This instruction exists because models invent plausible package names: a USENIX Security 2025 study measured 5.2% to 21.7% of suggested package names as nonexistent, depending on model and ecosystem.
|
||||
|
||||
## When to check
|
||||
|
||||
- Before adding a package to a dependency file (`requirements.txt`, `pyproject.toml`, `package.json`) or running an install command for a package you did not choose yourself.
|
||||
- Before writing an `import`, `require`, or `from ... import` for a third-party package.
|
||||
- Before calling a function, class, or constant you cannot confirm exists in the target package.
|
||||
- When a build fails on a missing package or symbol: check the name before changing anything else.
|
||||
|
||||
Skip the check for standard library modules, local project modules, and names already verified this session.
|
||||
|
||||
## How to check
|
||||
|
||||
Free keyless endpoint, no account or API key. Quota: 25 calls per day per client IP, reset 00:00 UTC.
|
||||
|
||||
1. Package check: POST `https://attester.dev/demo/v1/package/exists` with body `{"ecosystem": "pypi" | "npm", "name": "<name>"}`. Proceed only when `exists` is `true`.
|
||||
2. Symbol check: POST `https://attester.dev/demo/v1/symbol/exists` with body `{"ecosystem": "pypi" | "npm", "package": "<package>", "symbol": "<symbol>"}`. On a miss, prefer the `closest_match` suggestions over inventing variants.
|
||||
|
||||
On HTTP 429 (daily quota spent) or on network failure: state that the check was skipped and why, then continue with the most conservative option (prefer well-known packages and pinned versions).
|
||||
|
||||
## What to do with answers
|
||||
|
||||
- `exists: true`: proceed. When pinning, prefer the version in `latest_version`.
|
||||
- `exists: false`: do not install or import. Report the negative to the user together with the oracle's closest real names (`adjacent_to`, `closest_match`) and ask which one was meant.
|
||||
- `typosquat_adjacent: true`: treat as a strong signal that the name is a typo or a hallucination. Never install the flagged name.
|
||||
|
||||
## Higher volume
|
||||
|
||||
The free tier covers normal editing sessions. A paid route without the daily cap exists for high-volume use; see the service docs for details.
|
||||
Reference in New Issue
Block a user