skvil_scan
Submit security scan results for AI agent skills to the Skvil reputation network. Contributes to community reputation scores by providing accurate security findings and assessments.
Instructions
Submit security scan results for an AI agent skill to the Skvil reputation network. Contributes to the community reputation score (EMA). Requires an API key (use skvil_register first). The server recomputes the score from findings — always provide accurate findings.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes | Skill name | |
| composite_hash | Yes | SHA-256 composite hash of the skill (e.g. "sha256:4a2f8b...c81e") | |
| file_count | Yes | Number of files in the skill | |
| file_hashes | Yes | Map of relative file paths to their SHA-256 hex hashes | |
| score | Yes | Computed security score (0-100) | |
| risk_level | Yes | Overall risk assessment | |
| findings | No | Security findings detected in the skill | |
| frontmatter | No | SKILL.md frontmatter metadata (optional) | |
| skill_url | Yes | Source URL of the skill (e.g. "https://github.com/user/repo") | |
| provider | Yes | Platform hosting the skill | |
| agent | Yes | Agent platform submitting the scan (e.g. "claude", "codex", "openclaw") |
Implementation Reference
- src/tools.ts:336-414 (handler)Main handler implementation for skvil_scan tool. Registers the tool with input schema (Zod validation) including name, composite_hash, file_count, file_hashes, score, risk_level, findings, frontmatter, skill_url, provider, and agent. The handler calls api.scan() and formats the response with scan_id, reputation_score, total_scans, and certification status.
server.tool( 'skvil_scan', 'Submit security scan results for an AI agent skill to the Skvil reputation ' + 'network. Contributes to the community reputation score (EMA). Requires an ' + 'API key (use skvil_register first). The server recomputes the score from ' + 'findings — always provide accurate findings.', { name: z.string().max(256).describe('Skill name'), composite_hash: hashSchema, file_count: z.number().int().min(0).max(10000).describe('Number of files in the skill'), file_hashes: z .record( z .string() .max(500) .regex(/^[a-zA-Z0-9_\-./]+$/, 'Invalid file path'), z.string().regex(/^[a-f0-9]{64}$/, 'Must be 64 hex characters'), ) .describe('Map of relative file paths to their SHA-256 hex hashes'), score: z.number().int().min(0).max(100).describe('Computed security score (0-100)'), risk_level: z.enum(['safe', 'caution', 'danger']).describe('Overall risk assessment'), findings: z .array( z.object({ severity: z.enum(['critical', 'high', 'medium', 'low']), category: z.string().max(100), description: z.string().max(1000), file: z.string().max(500), line: z.number().int().optional(), }), ) .max(500) .default([]) .describe('Security findings detected in the skill'), frontmatter: z .record(z.union([z.string(), z.number(), z.boolean(), z.null()])) .optional() .describe('SKILL.md frontmatter metadata (optional)'), skill_url: z .string() .max(512) .regex( /^https:\/\/(github\.com|gitlab\.com|clawhub\.ai)\/[^/]+\/[^/].*$/, 'Must be a GitHub, GitLab, or ClawHub HTTPS URL', ) .describe('Source URL of the skill (e.g. "https://github.com/user/repo")'), provider: z .enum(['github', 'gitlab', 'clawhub']) .describe('Platform hosting the skill'), agent: z .string() .max(50) .describe('Agent platform submitting the scan (e.g. "claude", "codex", "openclaw")'), }, async (params) => { try { const result = await api.scan(params); const lines = [ '**Scan submitted successfully**\n', `- **Scan ID:** ${result.scan_id}`, `- **Updated reputation:** ${formatScore(result.reputation_score)}`, `- **Total community scans:** ${result.total_scans}`, ]; if (result.certification) { lines.push(`- **Certification:** ${result.certification}`); } lines.push( '\nYour scan contributes to the community reputation via exponential ' + 'moving average (EMA). Thank you for helping secure the AI skill ecosystem.', ); return { content: [{ type: 'text', text: lines.join('\n') }] }; } catch (error) { return { content: [{ type: 'text', text: formatError('scan', error) }], isError: true }; } }, ); - src/types.ts:73-100 (schema)Type definitions for skvil_scan input/output. ScanPayload (lines 81-93) defines the expected input structure with name, composite_hash, file_count, file_hashes, score, risk_level, findings array, optional frontmatter, skill_url, provider, and agent. ScanResponse (lines 95-100) defines the output with scan_id, reputation_score, total_scans, and certification.
export interface ScanFinding { severity: 'critical' | 'high' | 'medium' | 'low'; category: string; description: string; file: string; line?: number; } export interface ScanPayload { name: string; composite_hash: string; file_count: number; file_hashes: Record<string, string>; score: number; risk_level: 'safe' | 'caution' | 'danger'; findings: ScanFinding[]; frontmatter?: Record<string, unknown>; skill_url: string; provider: 'github' | 'gitlab' | 'clawhub'; agent: string; } export interface ScanResponse { scan_id: number; reputation_score: number; total_scans: number; certification: string | null; } - src/api.ts:151-154 (helper)API helper function that performs the actual HTTP POST request to the Skvil /scan endpoint. Takes a ScanPayload, authenticates with API key, and returns a ScanResponse. Uses the shared request() function with retry logic and error handling.
/** Submit scan results for a skill. */ export async function scan(payload: ScanPayload): Promise<ScanResponse> { return request<ScanResponse>('POST', '/scan', { body: payload, auth: true }); } - src/tools.ts:6-11 (schema)Shared hashSchema definition used by skvil_scan and other tools. Validates SHA-256 composite hash format: 'sha256:' prefix followed by 64 hexadecimal characters. This schema is reused in the composite_hash parameter of skvil_scan.
const HASH_PATTERN = /^sha256:[a-f0-9]{64}$/; const hashSchema = z .string() .regex(HASH_PATTERN, 'Must be "sha256:" followed by 64 hex characters') .describe('SHA-256 composite hash of the skill (e.g. "sha256:4a2f8b...c81e")');