/** * Skills-guard — trust-aware static security scanner for externally-sourced * skills. TypeScript port of Hermes `tools/skills_guard.py`. * * Every skill downloaded from a federation peer or community URL passes * through {@link scanSkill} before any disk write. The scanner runs the * 120-pattern threat table (see {@link ./skills-guard-patterns}) on every * scannable file plus structural checks (file count, size, binary blobs, * symlink escapes, invisible unicode). * * Install gating then runs {@link shouldAllowInstall} against the Hermes * `INSTALL_POLICY` matrix: * * | trust level | safe | caution | dangerous | * |---------------|-------|---------|-----------| * | builtin | allow | allow | allow | * | trusted | allow | allow | block | * | community | allow | block | block | * | agent-created | allow | allow | ask | * * Wire site: `packages/caamp/src/commands/skills/install.ts` calls * {@link scanSkill} BEFORE the canonical filesystem copy so a `block` * verdict has zero side-effects. * * @task T9730 * @epic T9564 * @saga T9560 * @architecture docs/skills/federation.md */ import { type FindingCategory, type FindingSeverity } from './skills-guard-patterns.js'; /** * Trust level of a skill source. * * Maps from a federation entry's `trust` field (verified → trusted) plus the * two special tiers `builtin` (ships with CLEO) and `agent-created` (produced * by `skill_manage` tool flows). Mirrors `INSTALL_POLICY` row labels in the * Hermes source. */ export type SkillTrustLevel = 'builtin' | 'trusted' | 'community' | 'agent-created'; /** * Overall verdict assigned by {@link scanSkill} based on the worst finding. * * - `safe` — zero findings. * - `caution` — at least one `high` finding, no critical. * - `dangerous` — at least one `critical` finding. * * Identical rule to `_determine_verdict` in `skills_guard.py`. */ export type ScanVerdict = 'safe' | 'caution' | 'dangerous'; /** * Install policy decision produced by {@link shouldAllowInstall}. * * - `allow` — proceed with install. * - `block` — refuse install (operator can re-run with `--force`). * - `ask` — prompt the operator (used by `agent-created` × dangerous). */ export type InstallDecision = 'allow' | 'block' | 'ask'; /** * One regex hit (or structural anomaly) discovered while scanning a skill. * * Field order matches the Hermes `Finding` dataclass so diffs between the * two implementations stay readable. */ export interface Finding { /** Stable identifier matching the pattern table or structural check. */ readonly patternId: string; /** Severity assigned by the pattern (or `critical` for structural escapes). */ readonly severity: FindingSeverity; /** Threat category. */ readonly category: FindingCategory; /** Path relative to the scanned skill root. */ readonly file: string; /** 1-based line number (`0` for directory-level findings). */ readonly line: number; /** First ≤120 chars of the matching content (or summary for structural hits). */ readonly match: string; /** Human-readable description (mirrors the pattern table). */ readonly description: string; } /** * Result returned by {@link scanSkill}. * * `verdict` is derived from `findings` per the Hermes rules so callers can * trust the verdict-only fast path without inspecting every finding. */ export interface ScanResult { /** Skill identifier (basename of the scanned path). */ readonly skillName: string; /** Caller-supplied source identifier (URL, repo, `agent-created`). */ readonly source: string; /** Trust level resolved from `source` via {@link resolveTrustLevel}. */ readonly trustLevel: SkillTrustLevel; /** Overall verdict — see {@link ScanVerdict}. */ readonly verdict: ScanVerdict; /** All findings, in discovery order. */ readonly findings: readonly Finding[]; /** ISO-8601 timestamp marking when the scan ran. */ readonly scannedAt: string; /** One-line human-readable summary. */ readonly summary: string; } /** * Trusted repository allow-list — identical set to Hermes `TRUSTED_REPOS`. * * Sources matching one of these prefixes are auto-promoted to `trusted`, * allowing `caution` verdicts to install without operator prompts. */ export declare const TRUSTED_REPOS: ReadonlySet; /** * Map a source identifier to a {@link SkillTrustLevel}. * * Resolution rules mirror Hermes `_resolve_trust_level`: * 1. `agent-created` → `agent-created`. * 2. `official/*` or `official` → `builtin`. * 3. Prefix-match against {@link TRUSTED_REPOS} → `trusted`. * 4. Strip `skills-sh/`/`skils-sh/`/etc. prefix aliases, retry rule 3. * 5. Fallback → `community`. * * @param source - Source identifier (URL, `owner/repo`, federation URL). * @returns The resolved trust level — never `undefined`. * * @task T9730 */ export declare function resolveTrustLevel(source: string): SkillTrustLevel; /** * Scan a single file for threat patterns and invisible unicode characters. * * Returns an empty array for files whose extension is not in * {@link SCANNABLE_EXTENSIONS} (unless the basename is `SKILL.md`), matching * the Hermes early-return in `scan_file`. * * @param filePath - Absolute path to the file. * @param relPath - Path to render in findings; defaults to `basename(filePath)`. * @returns Deduplicated findings — one per `(patternId, line)` pair. * * @task T9730 */ export declare function scanFile(filePath: string, relPath?: string): Finding[]; /** * Scan a skill directory (or single file) for threats. * * Runs structural checks then pattern matching, returning a fully-populated * {@link ScanResult}. Never throws on individual file I/O errors — bad files * are simply skipped so a single broken symlink can't crash the scanner. * * @param skillPath - Absolute path to the skill root directory or file. * @param source - Source identifier — drives {@link resolveTrustLevel}. * Defaults to `'community'` (the most conservative tier). * @returns Populated scan result with verdict + findings. * * @task T9730 */ export declare function scanSkill(skillPath: string, source?: string): ScanResult; /** * Decision shape returned by {@link shouldAllowInstall}. * * `reason` is always populated with a human-readable explanation suitable for * CLI rendering. Callers MAY surface `reason` verbatim in error envelopes. */ export interface InstallGateDecision { /** Final action — see {@link InstallDecision}. */ readonly decision: InstallDecision; /** Human-readable rationale. */ readonly reason: string; } /** * Decide whether a scan result allows installation under the Hermes * {@link INSTALL_POLICY} matrix. * * `force=true` flips a `block` decision to `allow` (the caller MUST log a * bypass entry in `.cleo/audit/skill-trust-bypass.jsonl` — see * {@link recordTrustBypass}). `ask` decisions are NEVER auto-allowed by * force; the operator must answer the prompt explicitly. * * @param result - The scan result. * @param force - Allow operator override of `block` decisions. * @returns Composite decision shape. * * @task T9730 */ export declare function shouldAllowInstall(result: ScanResult, force?: boolean): InstallGateDecision; /** * Compute a deterministic short SHA-256 hash of every file in a skill * directory for integrity tracking. Mirrors Hermes `content_hash`. * * @param skillPath - Skill root directory or single file. * @returns Hash prefixed with `sha256:` and truncated to 16 hex chars. */ export declare function contentHash(skillPath: string): string; /** * Format a scan result as a compact human-readable report. * * Output mirrors the Hermes `format_scan_report` shape so identical fixtures * produce identical reports for parity testing. */ export declare function formatScanReport(result: ScanResult): string; //# sourceMappingURL=skills-guard.d.ts.map