/** * Typosquat / impersonation detector for skill names * @module @skillsmith/core/security/scanner/typosquat * * SMI-595: no Levenshtein-distance / character-substitution / homoglyph * name-comparison detector against a popular-skill-name list existed * anywhere in this codebase prior to this module — content-based scanning * (`SecurityScanner`) cannot catch a name-only impersonation attempt where * the skill's actual content is benign (e.g. `anthopic/claude-helper` or * `аnthropic/skill` with a Cyrillic `а`). * * Three independent checks, deliberately NOT conflated: * * 1. **Exact-skeleton impersonation** — fires ONLY on exact confusable-folded * equality to a reference name, never on substring/affix match. This is a * deliberately narrow rule: `anthropic-community-mcp`, `unofficial-claude- * tools`, `awesome-gemini-skills` are normal, non-malicious community * naming conventions that happen to contain a brand token and must NOT be * blocked by this rule. * 2. **Levenshtein edit-distance ≤2** — catches near-miss variants * (`anthropc`, `anthropic1`, `anthropci`) that don't fold to an exact * skeleton match but are still suspiciously close to a reference name. * 3. **Authority-claiming affix** — INDEPENDENT of #1: a candidate containing * a reference brand token PLUS an affix that claims official status * (`-official`, `-verified`, `-authentic`, `-genuine`) is flagged * regardless of whether it passes the exact-skeleton check, because the * affix itself is the impersonation vector. Benign functional affixes * (`-mcp`, `-tools`, `-community`) do NOT trigger this — they simply * aren't in the curated affix list. * * Consumer-surface decision (Wave 1 Step 6): the install-time scanner path * (risk-score wiring — see types.ts/weights.ts/SecurityScanner.helpers.ts) is * the primary *enforcement* surface (blocks/warns before a skill lands on * disk). `skill_audit` (existing MCP tool) is the recommended primary * *consumer-facing* surface for on-demand querying of typosquat status on an * already-installed skill — live-wiring that tool to this detector is * deferred to a filed follow-up (SMI-5711), out of this wave's scope. * * Integration note: `SecurityScanner.scan()` takes `content` only, not a * skill name/author — this wave does NOT change that signature or thread a * live reference list through `scripts/skill-scanner/scanner.ts`'s scan * pipeline (that would require sourcing real HIGH_TRUST_OWNERS-published * skill data and real install-count data, which is a live-data/infra * integration, not a detector-design concern). This module is a * self-contained, fully-tested unit ready to be wired into that pipeline as a * follow-up. */ import type { SecurityFinding, TyposquatEnforcementMode } from './types.js'; export type { TyposquatEnforcementMode }; /** * Brand names that are NOT derivable from `HIGH_TRUST_OWNERS`' GitHub owner * slugs (`signal-of-intent.ts`) or from the installed-skill corpus at all — * the product/brand a typosquat would actually target (`anthropic`, `claude`, * `gemini`) differs from the GitHub org slug that publishes it (`anthropics`, * `google-gemini`). Folded into the reference list * (`typosquat-reference-list.ts`) as bare-brand reference entries, and also * used as the curated "brand token" corpus for the authority-claiming-affix * check below (§1 rule 3) — a narrower, independent check from the * exact-skeleton rule (§1 rule 1). * * Values cross-checked against `HIGH_TRUST_OWNERS` * (`packages/core/src/scripts/github-import/signal-of-intent.ts`) — kept in * sync manually, same convention as that file's own documented * cross-boundary-import constraint. */ export declare const BRAND_ALIASES: Readonly>; /** * A small, curated "claims official status" affix list. Deliberately narrow — * benign functional affixes (`mcp`, `tools`, `community`, `helper`, ...) * simply aren't in this list, so they never trigger this check. * * SMI-6033 Wave 1 (Gap 6): exported (was module-private) so the decoy/ * misdirection detector (`SecurityScanner.decoy.ts`, Wave 4) can reuse the * same curated affix corpus rather than duplicating it. */ export declare const AUTHORITY_CLAIMING_AFFIXES: ReadonlySet; /** * Standard iterative-DP Levenshtein edit distance (insertion/deletion/ * substitution). Inputs here are short skill-name strings, so an O(n*m) table * is more than fast enough — no early-exit optimization needed. */ export declare function levenshteinDistance(a: string, b: string): number; /** * Run all three checks against a single candidate skill name/id. Returns * RAW findings (uncapped severity) — callers apply * `applyTyposquatEnforcementMode()` before surfacing them, per the * `typosquatEnforcementMode` rollout config (§6). * * `referenceNames` should already be lowercase (as produced by * `buildTyposquatReferenceList()` in `typosquat-reference-list.ts`). */ export declare function scanTyposquat(candidateName: string, referenceNames: ReadonlySet): SecurityFinding[]; /** SMI-595: default rollout mode — shadow mode, matching the * `concurrency-audit-pr.yml` precedent (shadow for a period, then promote). */ export declare const DEFAULT_TYPOSQUAT_ENFORCEMENT_MODE: TyposquatEnforcementMode; /** Resolve the effective enforcement mode, applying the default when unset. */ export declare function resolveTyposquatEnforcementMode(mode?: TyposquatEnforcementMode): TyposquatEnforcementMode; /** * Apply the rollout mode to raw typosquat findings: * - `off` — discard all findings. * - `warn` — cap severity at `medium` regardless of the raw detector's * confidence (confidence is left untouched — only severity is capped). * - `block` — pass findings through at their raw severity. */ export declare function applyTyposquatEnforcementMode(findings: SecurityFinding[], mode?: TyposquatEnforcementMode): SecurityFinding[]; /** * Convenience one-shot: detect + apply the enforcement mode in a single call. */ export declare function detectTyposquat(candidateName: string, referenceNames: ReadonlySet, mode?: TyposquatEnforcementMode): SecurityFinding[]; //# sourceMappingURL=typosquat.d.ts.map