sn-implementation — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited sn-implementation (Agent Skill) and scored it 92/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 2 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 2 flagged
The text {match} tells the agent to skip the normal "ask the user first" gate. Used adversarially it removes the human-in-the-loop check before destructive or sensitive actions, turning a normally-gated agent into a fire-and-forget executor.
The text {match} tells the agent to skip the normal "ask the user first" gate. Used adversarially it removes the human-in-the-loop check before destructive or sensitive actions, turning a normally-gated agent into a fire-and-forget executor.
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Before writing any code, identify the layer you're editing and apply its rules.
signnow_client/models/)BaseModel, Field, validators, type aliases, __all__ exports.sn_mcp_server.* or signnow_client/client*.py. Any business logic.signnow_client/client_*.py)self._get(), self._post(), self._put(), self._post_multipart() from client_base.py. Import models from signnow_client/models/. Use exceptions from signnow_client/exceptions.py.sn_mcp_server.* (upward import). Direct httpx calls. Starlette imports. MCP logic.token: str and passes Authorization: Bearer {token}.sn_mcp_server/tools/models.py)BaseModel, Field. Type references from signnow_client/models/.sn_mcp_server/tools/<feature>.py)SignNowAPIClient, tools/models.py. Pure logic, data transformation.tools/<feature>.py. Resolve tokens. Module-level mutable state. Generic errors.(client: SignNowAPIClient, token: str, ...) → return curated DTOs.sn_mcp_server/tools/signnow.py)_get_token_and_client(), delegate to tools/<feature>.py, use Annotated[..., Field(...)] for params.sn_mcp_server/auth.py, token_provider.py)signnow_client, config.sn_mcp_server/app.py, cli.py)server.py, config.py, auth.py.Any unless wrapping untyped externals. Use str | None over Optional[str]. Use Annotated[..., Field(...)] for MCP tool parameters.__all__ in model files. One logical unit per file.tools/utils.py or signnow_client/utils.py at 3+ usages. Three similar lines are better than a premature abstraction.# ✅ Token is a parameter, no transport awareness
def _get_document(client: SignNowAPIClient, token: str, document_id: str) -> DocumentResponse:
...
# ❌ Importing Starlette, accessing headers — FORBIDDEN in tools
from starlette.requests import Request
def _get_document(request: Request, document_id: str) -> DocumentResponse:
token = request.headers["authorization"]# ✅ Masked for diagnostics
from sn_mcp_server.config import _mask_secret_value
logger.info(f"Using token: {_mask_secret_value(token)}")
# ❌ Raw secret — FORBIDDEN
logger.info(f"Using token: {token}")
raise ValueError(f"Auth failed with token {token}")When fixing a documented bug:
sn-mcp serve / sn-mcp http.Pre-commit runs: ruff check → ruff format → black → mypy --strict. Every file you create or edit must pass all four before committing.
# 1. Always first line in every file
from __future__ import annotations
# 2. Order: stdlib → third-party → local. One blank line between groups.
# ruff (I001) enforces this automatically — just keep groups separate.
import json
from pathlib import Path
from typing import Any
import pytest
import respx
from signnow_client import SignNowAPIClient# ✅ All public functions must have full annotations
def get_link(client: SignNowAPIClient, token: str, doc_id: str) -> str:
...
# ✅ Use X | Y syntax (UP007), not Optional or Union
def foo(value: str | None = None) -> dict[str, Any]:
...
# ✅ ANN401: avoid bare Any — use it only to wrap untyped externals
# If you must, add a comment explaining why
def _handle(data: dict[str, Any]) -> None: # Any because SignNow API schema varies
...
# ❌ Missing return type → ruff ANN201 + mypy error
def bad(x: str):
return x`self` and `cls` are exempt — ANN101/ANN102 are in lint.ignore. Do NOT annotate them.
# ✅ Import fixture types from their actual packages
from collections.abc import Callable, Generator
import respx
from signnow_client import SignNowAPIClient
# ✅ All test method params must be typed, return -> None
def test_something(
self,
client: SignNowAPIClient,
mock_api: respx.MockRouter,
token: str,
load_fixture: Callable[[str], dict[str, Any]],
) -> None:
...
# ✅ Fixture return types
@pytest.fixture()
def mock_api() -> Generator[respx.MockRouter, None, None]:
with respx.mock(...) as router:
yield router
@pytest.fixture()
def load_fixture() -> Callable[[str], dict[str, Any]]:
return _load_fixture# ✅ Suppress with noqa — it's intentional fake test data
FAKE_TOKEN = "test-token" # noqa: S105
cfg = SignNowConfig.model_construct(client_secret="test_secret") # noqa: S106# ✅ Suppress the Any return from json.loads
return json.loads(path.read_text()) # type: ignore[no-any-return][[tool.mypy.overrides]] disables misc for tests.* — so @pytest.fixture() decorators won't trigger mypy. No action needed; just keep the full type annotations above.
Complete ALL before declaring done:
ruff check src/ tests/ 2>&1 — must passruff format --check src/ tests/ 2>&1 — must passmypy src/ tests/ 2>&1 — must passscripts/verify-fix.py → import and call your new function with mock datapytest tests/ -v 2>&1Do not ask the user to test. YOU test.
@see .plans/).tools/__init__.py and document in README.md.AGENTS.md.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.