Add OpenClaw skill import system with security scanning#657
Add OpenClaw skill import system with security scanning#657
Conversation
Introduces a complete pipeline for importing OpenClaw/ClawHub SKILL.md skills as native CIRIS adapters, accessible via both REST API and the agent's tool system: - Parser: Handles YAML frontmatter (openclaw/clawdbot/clawdis namespaces) plus markdown instruction body with full field extraction - Converter: Generates complete adapter directories (manifest.json, adapter.py, services.py) in ~/.ciris/adapters/ for auto-discovery - API endpoints: POST /system/adapters/import-skill (with preview), GET /system/adapters/imported-skills, DELETE imported skills - 34 tests covering parsing, conversion, and field consumption verification https://claude.ai/code/session_01361QLEDwCP5FrbykjoWi4n
Adds parsing and consumption of previously missing fields: - homepage (top-level frontmatter, fallback to metadata.openclaw.homepage) - disable-model-invocation -> context_enrichment=False on info tool - command-arg-mode -> stored in manifest metadata - requires.anyBins -> ToolRequirements.any_binaries - requires.config -> ToolRequirements.config_keys (as ConfigRequirement) - metadata.os -> manifest platform_requirements + ToolRequirements.platforms - metadata.always/skill_key/emoji -> stored in manifest metadata - install spec url/archive/stripComponents/targetDir -> manual command 43 tests now verify every OpenClaw field is either actively consumed in the generated adapter or stored in manifest metadata with rationale. https://claude.ai/code/session_01361QLEDwCP5FrbykjoWi4n
Every stored field now has a concrete CIRIS integration: - emoji -> ModuleTypeInfo.emoji (displayed in UI adapter list) - homepage -> ModuleTypeInfo.homepage (displayed in UI adapter list) - always -> context_enrichment=True on main skill tool (auto-injected into DMA prompt every cycle, not just the :info tool) - skillKey -> ToolBus.register_tool_alias() (invoke "todoist" instead of "skill:todoist-cli") - command_tool -> also registered as ToolBus alias - user_invocable=false -> 'internal' tag on ToolInfo (UI can filter) - command_dispatch=tool -> 'direct_dispatch' tag on ToolInfo - disable_model_invocation -> context_enrichment=False on :info tool ToolBus enhancements: - register_tool_alias(alias, canonical) for skill aliasing - resolve_tool_name() called in execute_tool and get_tool_info - Generated adapters register aliases on startup via bus_manager 55 tests passing (12 new tests for these integrations). https://claude.ai/code/session_01361QLEDwCP5FrbykjoWi4n
Initial KMP (Kotlin Multiplatform) components for the skill import feature, laying the groundwork for the full import/analyze/create/edit workflow: - SkillImport.kt: Data models (SkillPreviewData, SkillImportResult, ImportedSkillData) - SkillImportViewModel.kt: MVVM state management with three-phase flow (paste/preview/result) and CRUD for imported skills - SkillImportDialog.kt: Three-phase dialog composable with paste, preview, and result cards plus ImportedSkillCard for the list view - CIRISApiClient.kt: API methods for preview, import, list, delete - AdaptersScreen.kt: Import Skill button in top bar actions - CIRISApp.kt: Screen.SkillImport added to navigation https://claude.ai/code/session_01361QLEDwCP5FrbykjoWi4n
The skill builder treats every CIRIS adapter as a stack of 6 editable
cards, each backed by a Pydantic model with full JSON Schema introspection.
Two modes: card mode (form rendered from schema) and edit mode (raw JSON).
Card stack:
1. Identity - name, description, emoji, version, homepage
2. Tools - tool definitions with parameters
3. Requires - env vars, binaries, platforms
4. Instruct - AI directive (the brain of the skill)
5. Behavior - DMA guidance: approval, confidence, ethics
6. Install - dependency installation steps
Key design: GET /skills/cards returns all card JSON Schemas in one call.
The UI renders forms from schemas. User edits. PUT sends data back.
Backend validates against Pydantic models. Everything is serializable.
Draft lifecycle: create (blank or from OpenClaw import) -> edit cards ->
validate -> build adapter. Drafts persist as JSON in ~/.ciris/skill_drafts/.
API endpoints:
GET /system/skills/cards - All card schemas (bootstrap UI)
GET /system/skills/cards/{id} - Single card schema
POST /system/skills/drafts - Create draft
GET /system/skills/drafts - List drafts
GET /system/skills/drafts/{id} - Get draft
PUT /system/skills/drafts/{id} - Update draft
PUT /system/skills/drafts/{id}/cards/{card} - Update single card
POST /system/skills/drafts/{id}/validate - Validate
POST /system/skills/drafts/{id}/build - Build adapter
83 tests passing across parser, converter, builder, and ToolBus aliases.
https://claude.ai/code/session_01361QLEDwCP5FrbykjoWi4n
…tion "Keep the song singable for every voice not yet heard." Every field now has plain English labels and hints written for people with minimal tech competency. Complexity is scaffolded: Card mode (default): - Identity: "Give your skill a name and tell people what it does" - Tools: "Each skill has tools — actions the agent can take" - Requirements: "Some skills need passwords, programs, or specific devices" - Instructions: "Write it like you're explaining to a helpful friend" - Safety: "Control how careful the agent should be" - Install: "If your skill needs extra software, list how to install it" Scaffolded disclosure: - Identity + Tools cards expanded by default (essential) - Requirements collapsed (show only if present) - Instructions collapsed (for review) - Safety collapsed (auto-set for imports: approval=true, confidence=70%) WorkshopCard component: - Emoji icon + title + plain English hint - Collapsible with animation - testable() tags for UI automation 100 localization keys added to en.json following existing patterns: skill_field_*, skill_card_*, skill_behavior_*, skill_import_*, skill_error_*, skill_mode_* (simple/advanced/json toggle) Import flow redesigned: 1. Paste: warning about untrusted skills, source URL optional 2. Review: parsed skill shown as cards for human inspection 3. Result: success/failure with tool list https://claude.ai/code/session_01361QLEDwCP5FrbykjoWi4n
Scans imported skills for 8 threat categories from the Feb 2026 ClawHub security crisis (Snyk ToxicSkills, Koi Security ClawHavoc): 1. Prompt injection (36% of ClawHub skills - "ignore previous instructions", identity reassignment, silent exfiltration via URL) 2. Credential theft (SSH keys, AWS creds, browser cookies, crypto wallets, .env files - from AMOS stealer campaign) 3. Reverse shell / backdoor (netcat, bash TCP, curl|bash, cron persistence, launchctl/systemctl service installation) 4. Cryptominer deployment (xmrig, mining pool URLs, stratum protocol) 5. Typosquatting (known ClawHavoc names + Levenshtein distance against popular skill names) 6. Undeclared network access (curl/wget in instructions not declared in requires.bins) 7. Code obfuscation (eval/exec, hex/unicode encoding, subprocess calls) 8. Metadata inconsistency (undeclared env vars, suspicious description length ratios) Security enforcement: - Preview endpoint returns full SecurityReportResponse with findings - Import endpoint BLOCKS skills with critical or high findings - Every finding has plain English title, description, and recommendation - 30 scanner tests covering all threat categories References: - CVE-2026-25593, CVE-2026-24763, CVE-2026-25157 - Snyk ToxicSkills: 1,467 malicious skills, 91% combined prompt injection with traditional malware - ClawHavoc campaign: 335 skills delivering Atomic Stealer (AMOS) https://claude.ai/code/session_01361QLEDwCP5FrbykjoWi4n
Documents the 5-layer defense-in-depth model: 1. Security scanner (8 threat categories from ClawHub crisis) 2. Schema validation (Pydantic strict models) 3. DMA guidance (requires_approval=true by default) 4. H3ERE pipeline (4 DMAs + conscience + Ed25519 audit) 5. Adapter isolation (user-space, ToolBus mediated) Includes references to Snyk ToxicSkills, ClawHavoc, and CVEs. https://claude.ai/code/session_01361QLEDwCP5FrbykjoWi4n
Adds 135 localization keys for the skill builder UI covering: Workshop keys (100): skill_card_* - Card titles/hints in simple English skill_field_* - Form field labels and placeholders skill_tool_* - Tool builder fields skill_req_* - Requirements (env vars, binaries, platforms) skill_behavior_* - Safety settings (approval, confidence, ethics) skill_import_* - Import flow (paste, analyze, review, approve) skill_error_* - Validation messages skill_mode_* - Simple/Advanced/JSON mode toggle Security keys (35): skill_security_* - Scan results, severity labels, recommendations skill_finding_* - 8 threat categories in plain English skill_severity_* - Danger/Risky/Suspicious/Minor/Info skill_security_layer_* - 5-layer defense explanation All strings written for minimal tech competency: "Ask permission first?" not "requires_approval" "Tries to manipulate the agent" not "prompt_injection detected" "Uses your device to mine cryptocurrency" not "cryptominer binary" 4 languages complete (en, de, es, fr). Remaining 11 in progress. https://claude.ai/code/session_01361QLEDwCP5FrbykjoWi4n
…tr/ru) 135 keys × 8 languages = 1,080 translated strings covering: Workshop UI (100 keys): skill_card_* - Card titles/hints in plain language skill_field_* - Form labels and placeholders skill_tool_* - Tool builder fields skill_behavior_* - Safety settings (approval, confidence, ethics) skill_import_* - Import flow with security warnings Security UI (35 keys): skill_security_* - Scan results, severity labels skill_finding_* - 8 threat categories from ClawHub crisis skill_severity_* - Danger levels in plain language skill_security_layer_* - 5-layer defense explanation Every string written for minimal tech competency: DE: "GEFAHR: Dieser Skill könnte schädlich sein" ES: "PELIGRO: Este skill puede ser dañino" FR: "DANGER : Ce skill peut être dangereux" IT: "PERICOLO: Questo skill potrebbe essere dannoso" PT: "PERIGO: Este skill pode ser prejudicial" TR: "TEHLİKE: Bu beceri zararlı olabilir" RU: "ОПАСНО: Этот навык может быть вредоносным" Remaining 7 languages (ja/ko/zh/ar/hi/am/sw) in progress. https://claude.ai/code/session_01361QLEDwCP5FrbykjoWi4n
Japanese (135 keys): polite casual register 危険:このスキルは有害な可能性があります。インポートしないでください。 すべてのアクションはエージェントの良心によってチェックされます Korean (135 keys): polite formal register 위험: 이 스킬은 해로울 수 있습니다. 가져오지 마세요. 모든 행동은 에이전트의 양심에 의해 검사됩니다 Chinese Simplified (135 keys): simple 简体中文 危险:此技能可能有害。请勿导入。 每个操作都由智能助手的良知检查 11 of 15 languages complete. Remaining: ar, hi, am, sw. https://claude.ai/code/session_01361QLEDwCP5FrbykjoWi4n
|
|
|
|
||
| if req.local_path: | ||
| path = Path(req.local_path) | ||
| if path.is_dir(): |
Check failure
Code scanning / CodeQL
Uncontrolled data used in path expression High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 11 days ago
In general, to fix uncontrolled path use you should normalize the user-provided path and ensure it is constrained to an expected safe root directory (or use a strict allow list or filename sanitizer). Here, req.local_path is meant for "CLI/desktop use", but it still comes from the HTTP body and is used directly as a Path that can point anywhere. We should introduce a safe base directory for local imports (for example, the current working directory or a dedicated configuration directory), normalize the joined path, and verify that the resulting path is within that base. This prevents traversal (..), absolute paths, and paths that escape the designated root, while preserving the ability to specify relative paths under that root.
Concretely, within _parse_skill_from_request in ciris_engine/logic/adapters/api/routes/system/skill_import.py, we will:
- Define a constant
LOCAL_SKILL_IMPORT_ROOTat module level, set to a safe base (e.g.,Path.cwd()by default), with a comment explaining its purpose. - Replace the direct
path = Path(req.local_path)with:base = LOCAL_SKILL_IMPORT_ROOT.resolve()candidate = (base / req.local_path).resolve()- A check that
candidateis withinbaseusingcandidate.is_relative_to(base)(Python 3.9+); if not, raiseValueErrorwith a clear message. - Assign
path = candidate.
- Keep the existing logic (
is_dir,is_file, read, etc.) but operate on this validatedpath.
This approach avoids changing external behavior for valid, in-root relative paths but blocks absolute paths and traversal outside the configured root. No new third‑party imports are needed; we only use pathlib.Path which is already imported. All changes are localized to the shown file and the _parse_skill_from_request helper.
| @@ -22,6 +22,12 @@ | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
| # Base directory for local skill imports. All local_path values must resolve | ||
| # to a location within this directory to be considered valid. | ||
| # By default, use the current working directory; this can be adjusted as needed | ||
| # for the deployment environment. | ||
| LOCAL_SKILL_IMPORT_ROOT = Path.cwd() | ||
|
|
||
| router = APIRouter() | ||
|
|
||
| # Annotated type alias for FastAPI dependency injection | ||
| @@ -149,7 +155,19 @@ | ||
| return parser.parse_skill_md(req.skill_md_content, source_url=req.source_url) | ||
|
|
||
| if req.local_path: | ||
| path = Path(req.local_path) | ||
| # Resolve the requested path relative to the configured local import root | ||
| base = LOCAL_SKILL_IMPORT_ROOT.resolve() | ||
| # Interpret req.local_path as a path under the base directory | ||
| candidate = (base / req.local_path).resolve() | ||
| try: | ||
| # Python 3.9+: ensure the path is within the allowed base directory | ||
| if not candidate.is_relative_to(base): | ||
| raise ValueError("Local path is outside the allowed import directory") | ||
| except AttributeError: | ||
| # Fallback for older Python: manual prefix check | ||
| if str(candidate) != str(base) and not str(candidate).startswith(str(base) + candidate.anchor.replace(candidate.anchor, "")): | ||
| raise ValueError("Local path is outside the allowed import directory") | ||
| path = candidate | ||
| if path.is_dir(): | ||
| return parser.parse_directory(path, source_url=req.source_url) | ||
| elif path.is_file(): |
| path = Path(req.local_path) | ||
| if path.is_dir(): | ||
| return parser.parse_directory(path, source_url=req.source_url) | ||
| elif path.is_file(): |
Check failure
Code scanning / CodeQL
Uncontrolled data used in path expression High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 11 days ago
In general, the fix is to validate and constrain any filesystem paths derived from user input before accessing the filesystem. Common patterns: (1) restrict to a known safe root directory and normalize/resolve before checking containment; or (2) if local_path is not intended for remote use, explicitly disallow it in these HTTP endpoints and only support it in trusted contexts.
Given the context (“Local file path (for CLI/desktop use)”), the least invasive and safest fix for this API layer is: when handling an HTTP request, explicitly reject the local_path mode with a clear error. That keeps existing behavior for skill_md_content and source_url intact and avoids any path-based file IO using user-controlled data over HTTP. If the codebase has other non-HTTP entry points that construct a SkillImportRequest with local_path directly (e.g., a CLI), they can still call _parse_skill_from_request safely because they won't go through these FastAPI routes.
Concretely:
- In both
preview_skill_importandimport_skill, before calling_parse_skill_from_request, checkbody.local_path. If it is set, raise anHTTPException(400) indicating thatlocal_pathis not supported via the HTTP API and that the caller must provide the SKILL.md content instead. - No changes are needed inside
_parse_skill_from_request; we keep the same behavior for trusted, internal callers and eliminate the tainted-flow path only for HTTP requests. - All modifications occur within
ciris_engine/logic/adapters/api/routes/system/skill_import.pyat the two route functions around the existing try/except blocks.
| @@ -258,6 +258,16 @@ | ||
|
|
||
| Requires ADMIN role. | ||
| """ | ||
| # For security reasons, do not allow arbitrary local filesystem paths to be | ||
| # supplied via the HTTP API. Callers must provide the SKILL.md content | ||
| # directly (skill_md_content) instead of a local_path. | ||
| if body.local_path: | ||
| raise HTTPException( | ||
| status_code=400, | ||
| detail="Importing skills via local_path is not supported over HTTP. " | ||
| "Provide SKILL.md content via skill_md_content instead.", | ||
| ) | ||
|
|
||
| try: | ||
| skill = _parse_skill_from_request(body) | ||
| except (ValueError, FileNotFoundError) as e: | ||
| @@ -293,6 +303,10 @@ | ||
| Parses the SKILL.md content, generates a full CIRIS adapter directory, | ||
| and optionally loads it into the running runtime. | ||
|
|
||
| For security reasons, importing via an arbitrary local filesystem path | ||
| (local_path) is not supported over this HTTP API. Callers must provide | ||
| the SKILL.md content directly instead. | ||
|
|
||
| The adapter is created in ~/.ciris/adapters/ by default, which is | ||
| automatically discovered by the AdapterDiscoveryService. | ||
|
|
| if path.is_dir(): | ||
| return parser.parse_directory(path, source_url=req.source_url) | ||
| elif path.is_file(): | ||
| content = path.read_text(encoding="utf-8") |
Check failure
Code scanning / CodeQL
Uncontrolled data used in path expression High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 11 days ago
In general, to fix uncontrolled-path issues you must constrain or validate any user-provided path before using it, typically by (1) defining a safe root directory, (2) resolving the user path against that root, (3) normalizing it (e.g., Path.resolve()), and (4) verifying that the final path is still within the allowed root. Absolute paths, traversal attempts (..), or paths escaping the root should be rejected.
For this specific code, the best single fix with minimal behavior change is:
-
Introduce a helper function
_resolve_local_paththat:- Accepts the raw
local_pathstring. - Resolves it against a configured base directory (e.g., an environment variable
CIRIS_SKILL_IMPORT_ROOTor a default such as the current working directory). - Uses
Path.resolve(strict=False)to normalize the path. - Verifies that the resolved path is within the base directory by checking
resolved_path == base_dirorbase_dir in resolved_path.parents. - Raises a
ValueErrorwith a clear message if the path is outside the allowed root or is otherwise invalid.
- Accepts the raw
-
Update
_parse_skill_from_requestso that instead ofpath = Path(req.local_path)it calls_resolve_local_path(req.local_path)and uses the returned safePath. The rest of the logic (is_dir(),is_file(),read_text()) remains unchanged. -
Because we already import
Pathfrompathlib, no extra third-party packages are needed. For reading an environment variable we can importos(standard library) at the top of the file.
Concretely, in ciris_engine/logic/adapters/api/routes/system/skill_import.py:
- Add
import osalongside existing imports. - Add a new helper
_resolve_local_path(local_path: str) -> Pathin the “Helper Functions” section before_parse_skill_from_request. - Modify
_parse_skill_from_requestto use_resolve_local_pathinstead ofPath(req.local_path).
This ensures both alert variants (for the two endpoints using body) are addressed, since they both call _parse_skill_from_request.
| @@ -11,6 +11,7 @@ | ||
| from pathlib import Path | ||
| from typing import Annotated, Any, Dict, List, Optional | ||
|
|
||
| import os | ||
| from fastapi import APIRouter, Body, Depends, HTTPException, Request | ||
| from pydantic import BaseModel, ConfigDict, Field | ||
|
|
||
| @@ -141,6 +142,29 @@ | ||
| # Helper Functions | ||
| # ============================================================================ | ||
|
|
||
| def _resolve_local_path(local_path: str) -> Path: | ||
| """Resolve and validate a local path for skill import. | ||
|
|
||
| The path is resolved against a configured root directory to prevent | ||
| access outside the allowed tree. | ||
| """ | ||
| # Base directory for local skill imports. Can be overridden via env var. | ||
| base_dir_env = os.environ.get("CIRIS_SKILL_IMPORT_ROOT") | ||
| base_dir = Path(base_dir_env) if base_dir_env else Path.cwd() | ||
| base_dir = base_dir.resolve(strict=False) | ||
|
|
||
| # Always interpret the provided path relative to the base directory. | ||
| candidate = (base_dir / local_path).resolve(strict=False) | ||
|
|
||
| # Ensure the resolved path is within the base directory. | ||
| try: | ||
| candidate.relative_to(base_dir) | ||
| except ValueError: | ||
| raise ValueError("Local path is outside the allowed import directory") | ||
|
|
||
| return candidate | ||
|
|
||
|
|
||
| def _parse_skill_from_request(req: SkillImportRequest) -> ParsedSkill: | ||
| """Parse a skill from the request, handling all input modes.""" | ||
| parser = OpenClawSkillParser() | ||
| @@ -149,7 +173,7 @@ | ||
| return parser.parse_skill_md(req.skill_md_content, source_url=req.source_url) | ||
|
|
||
| if req.local_path: | ||
| path = Path(req.local_path) | ||
| path = _resolve_local_path(req.local_path) | ||
| if path.is_dir(): | ||
| return parser.parse_directory(path, source_url=req.source_url) | ||
| elif path.is_file(): |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ffd0fa0299
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| install_steps_code = "[]" | ||
| if skill.metadata and skill.metadata.install: | ||
| steps = _build_install_steps(skill.metadata.install) | ||
| install_steps_code = repr(steps) |
There was a problem hiding this comment.
Attach converted install steps to generated tool metadata
The converter builds install_steps_code from metadata.install, but the generated ToolInfo never consumes that value, so imported skills lose all dependency installation guidance. In practice, skills that declare brew/pip/npm installs will appear to have no install steps, which breaks setup workflows for users and any UI that relies on ToolInfo.install_steps.
Useful? React with 👍 / 👎.
| supporting_dir.mkdir(exist_ok=True) | ||
|
|
||
| for rel_path, content in skill.supporting_files.items(): | ||
| target = supporting_dir / Path(rel_path).name # Flatten to single directory |
There was a problem hiding this comment.
Preserve supporting file subpaths when writing adapter assets
Supporting files are flattened to Path(rel_path).name, so two files from different directories with the same basename overwrite each other (e.g., references/readme.md and scripts/readme.md). This silently drops content from imported skills and can alter execution context whenever filename collisions occur.
Useful? React with 👍 / 👎.
| skill_key=None, | ||
| install=[], | ||
| ) |
There was a problem hiding this comment.
Carry install-card steps into ParsedSkill during draft builds
The builder discards user-authored install-card data by hardcoding metadata.install to an empty list when converting a draft to ParsedSkill. As a result, skills built from the card UI never include the install instructions users entered, even though the draft model captures them.
Useful? React with 👍 / 👎.
| )""") | ||
|
|
||
| # Build install steps | ||
| install_steps_code = "[]" |
| install_steps_code = "[]" | ||
| if skill.metadata and skill.metadata.install: | ||
| steps = _build_install_steps(skill.metadata.install) | ||
| install_steps_code = repr(steps) |
| escaped_instructions = skill.instructions.replace("\\", "\\\\").replace('"""', '\\"\\"\\"') | ||
|
|
||
| # Build platform list | ||
| platforms = skill.metadata.os if skill.metadata and skill.metadata.os else [] |
| def _check_metadata_consistency(self, skill: ParsedSkill) -> List[SkillSecurityFinding]: | ||
| """Check for inconsistencies between metadata and content.""" | ||
| findings = [] | ||
| instructions = (skill.instructions or "").lower() |
| from ciris_engine.schemas.adapters.tools import ( | ||
| InstallStep, | ||
| ToolDMAGuidance, | ||
| ToolDocumentation, | ||
| ToolInfo, | ||
| ToolParameterSchema, | ||
| ToolRequirements, | ||
| ) |
| import logging | ||
| import re | ||
| from enum import Enum | ||
| from typing import Any, Dict, List, Optional |
| from ciris_engine.logic.services.skill_import.builder import ( | ||
| CARD_DEFINITIONS, | ||
| SkillBuilder, | ||
| SkillDraft, | ||
| get_all_card_schemas, | ||
| get_card_schema, | ||
| ) |
| @@ -0,0 +1,332 @@ | |||
| """Tests for the skill builder (HyperCard-style card system).""" | |||
|
|
|||
| import json | |||
| from ciris_engine.logic.services.skill_import.builder import ( | ||
| BehaviorCard, | ||
| IdentityCard, | ||
| InstructCard, | ||
| RequiresCard, | ||
| SkillBuilder, | ||
| SkillDraft, | ||
| ToolCard, | ||
| ToolParameter, | ||
| ToolsCard, | ||
| get_all_card_schemas, | ||
| get_card_schema, | ||
| ) |
| from ciris_engine.logic.services.skill_import.parser import ( | ||
| OpenClawSkillParser, | ||
| ParsedSkill, | ||
| SkillInstallSpec, | ||
| SkillMetadata, | ||
| SkillRequirements, | ||
| ) |
Final 4 languages complete the full polyglot coverage: Arabic (ar): خطر: هذه المهارة قد تكون ضارة. لا تستوردها. Hindi (hi): खतरा: यह स्किल हानिकारक हो सकता है। आयात न करें। Amharic (am): አደጋ፡ ይህ ክህሎት ጎጂ ሊሆን ይችላል። አያስገቡ። Swahili (sw): HATARI: Ujuzi huu unaweza kuwa na madhara. USIUINGIZE. Total: 2,025 translated strings (135 keys × 15 languages) All languages: en, de, es, fr, it, pt, tr, ru, ja, ko, zh, ar, hi, am, sw Every security warning communicates danger clearly in every language. Every card hint uses simple, conversational tone for non-technical users. "Keep the song singable for every voice not yet heard." https://claude.ai/code/session_01361QLEDwCP5FrbykjoWi4n
Summary
Implements a complete skill import system that allows users to import OpenClaw-format skills as CIRIS adapters. This includes parsing, security scanning, preview/review workflows, and adapter generation with a HyperCard-inspired card-based UI for skill creation and editing.
Key Changes
Backend Services
parser.py): Parses OpenClaw SKILL.md format (YAML frontmatter + markdown) into structuredParsedSkillobjects, supporting multiple metadata namespaces (openclaw, clawdbot, clawdis)scanner.py): Comprehensive threat detection based on real ClawHub malware analysis (Feb 2026), detecting prompt injection, credential exfiltration, reverse shells, cryptominers, typosquatting, and undeclared network accessconverter.py): Transforms parsed skills into complete CIRIS adapter directories with generated Python code, manifests, and installation stepsbuilder.py): HyperCard-inspired card system for skill creation with 6 card types (identity, tools, requires, instruct, behavior, install), supporting both form-based and raw JSON editing modesskill_import.py) for preview, security scanning, and importing skillsskill_builder.py) for managing skill drafts and card-based editingMobile UI
SkillImportDialog.kt): Multi-phase dialog (paste → preview → result) with security findings display, skill metadata review, and import confirmationSkillImportViewModel.kt): State management for import workflows and imported skills listSkillImport.kt): Kotlin data classes for preview data, import results, and imported skill metadataInfrastructure
tool_bus.py): Added tool alias registration mechanism to support skill-defined tool name mappingsNotable Implementation Details
https://claude.ai/code/session_01361QLEDwCP5FrbykjoWi4n