> ## Documentation Index
> Fetch the complete documentation index at: https://comis-feature-skill-archive-import.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Importing Skills

> Import a skill from a GitHub directory, a packaged archive, or an allowlisted registry through the staged, scan-first pipeline

Comis can install a skill you did not author yourself -- from a GitHub directory, a packaged archive, or an allowlisted registry that publishes a well-known skill index. Every import runs through **one staged pipeline** and lands the skill at the `imported` trust tier: a deliberately cautious posture that treats the skill's contents as untrusted until you say otherwise.

This page covers how to import, what actually lands on disk, the trust model an imported skill runs under, and -- honestly -- what the import-time scan does and does not prove.

## How to Import

There are two ways to import a skill.

**From the CLI:**

```bash theme={null}
# Import a skill from a GitHub directory URL (the default source)
comis skills import https://github.com/owner/repo/tree/main/skills/my-skill

# Import a packaged archive (.skill, .zip, or .tar/.tar.gz) by URL
comis skills import https://example.com/my-skill.skill --source archive

# Import a skill by name from an allowlisted registry's well-known index
comis skills import my-skill --source wellknown --registry https://registry.example

# Import an @owner/slug skill from ClawHub (allowlist the `clawhub` token first)
comis skills import @acme/pdf-extractor --source clawhub
```

**From an agent** with the `skills_manage` tool: the `import` action takes the same parameters. It stays behind the tool's admin-trust + approval + `orch:skill` triple gate -- importing is never a silent, unattended action.

### Sources

| Source             | What `<ref>` is                                                                        | How the bytes arrive                                                                                                                                                                            |
| ------------------ | -------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `github` (default) | A GitHub directory URL, e.g. `https://github.com/{owner}/{repo}/tree/{branch}/{path}`  | Fetched file-by-file from the GitHub Contents API (fixed host, bounded depth + file count)                                                                                                      |
| `archive`          | A URL to a `.skill` / `.zip` / `.tar` / `.tar.gz` archive                              | Fetched through the SSRF guard (DNS-pinned, metadata-blocked), then unpacked in-house against strict size caps                                                                                  |
| `wellknown`        | A **skill name** (not a URL), resolved from the `--registry` origin's well-known index | The registry's `/.well-known/skills/index.json` is fetched (SSRF-pinned, byte-capped), then each advertised file for that name — see [Registry import](#registry-import-well-known-index)       |
| `clawhub`          | An **`@owner/slug`** identifier (not a URL), resolved from ClawHub                     | The install decision and the scan **verdict** are fetched and evaluated first; only a non-blocked release is then downloaded (SSRF-pinned, byte-capped) — see [ClawHub import](#clawhub-import) |

An archive may also be supplied as base64 bytes (`archiveBytes`) rather than a URL -- this is how an uploaded file reaches the pipeline. Whichever way the bytes arrive, they funnel into the **same** acquire -> unpack -> map -> scan -> install pipeline.

### Registry import (well-known index)

`source: "wellknown"` installs a skill **by name** from a registry that publishes the `/.well-known/skills/index.json` convention. You give the registry origin with `--registry` (the tool parameter is `registry`), and `<ref>` is the skill **name** to look up in that registry's index -- not a URL. The index advertises each skill's name and its file paths; Comis fetches `index.json`, finds your named skill, and fetches its advertised files.

**The registry must be allowlisted first.** A well-known import only proceeds if the registry's origin is listed in the agent's [`skills.import.registries`](/reference/config-yaml#skills-agents-skills) allowlist. A registry that is **not** allowlisted refuses **flatly**, before any network fetch -- and this refusal is **not** overridable by `--confirm`. Allowlisting a registry is a deliberate config edit, kept out-of-band from the import call; `--confirm` narrowly acknowledges a content change on a re-import (below), it never authorizes a new registry. The allowlist is default-empty, so no registry imports happen until you add one.

Every fetch -- the index and each advertised file -- goes through the same SSRF guard (DNS-pinned, metadata-blocked) and byte caps as an archive import. Each advertised path is validated, and a **single** unsafe path (absolute, `..` traversal, or otherwise escaping) rejects the **whole** bundle rather than skipping the one file. The resolved files then enter the same acquire -> map -> scan -> install pipeline every source shares, so a well-known skill lands **prompt-only at the `imported` trust tier** with the same honest exec caveat -- importing it adds instructions the agent may read, never the authority to run anything. The provenance record additionally pins the **registry origin** the skill came from, and a listed well-known skill reports `source: "wellknown"` plus that registry in its provenance summary.

Re-importing the same `(registry, name)` follows the same re-import rule as any source: identical content is a no-op, and **changed** remote bytes refuse unless you pass `--confirm`, which swaps in the new content and re-pins the hash.

### ClawHub import

`source: "clawhub"` installs a skill from **ClawHub** by its `@owner/slug` identifier. `<ref>` is the `@owner/slug` -- not a URL -- and the registry is inferred, so you do not pass `--registry`. As with a well-known import, it only proceeds if the `clawhub` token is in the agent's [`skills.import.registries`](/reference/config-yaml#skills-agents-skills) allowlist; a non-allowlisted `clawhub` refuses **flatly**, and that refusal is **not** overridable by `--confirm`.

**The verdict is checked before the release downloads.** ClawHub resolution is an install-resolver flow: Comis fetches the install decision and the skill's scan **verdict**, evaluates the verdict, and only then downloads the release archive. A release whose verdict is **`malicious`**, **`blockedFromDownload`**, **`quarantined`**, or **`revoked`** (or is otherwise reason-flagged as blocked) is refused **before a single artifact byte is fetched** -- the download never happens. This is a hard security stop, not a warnable class: `--confirm` never overrides it.

**Non-official publishers must be acknowledged.** By default ([`requireOfficialPublisher`](/reference/config-yaml#skills-agents-skills) is `true`), a skill from a **non-official** publisher records `officialPublisher: false` and requires `--confirm` to install -- pulling an unofficial `@owner/slug` is a deliberate, acknowledged act, on a fresh install as much as on an update. Unlike a blocking verdict, this is a **warnable** class: acknowledged, not forbidden. When one import trips **both** warnable classes at once -- a non-official publisher **and** a changed-content re-import (a pin divergence) -- a single `--confirm` acknowledges **both**, and the response enumerates each acknowledged class in its `warnings`.

<Note>
  The official/non-official signal is **asserted by the registry itself** (its self-claimed publisher channel), so `requireOfficialPublisher` is a **publisher-provenance signal, not a cryptographic guarantee** -- a compromised registry could claim official. It is one honesty check among the layers below; the integrity floor an import actually rests on is the self-computed content hash (next) plus TLS, not this flag. Allowlisting a registry with `clawhub` is itself the deliberate trust decision.
</Note>

**Integrity.** When ClawHub sends a release content hash (`X-ClawHub-Artifact-Sha256`), Comis verifies the downloaded bytes against it and rejects a mismatch -- the header is checked **when present** and is never required. The always-present integrity floor is Comis's own content hash, computed over the installed file set and pinned in provenance (the release is fetched over TLS from `clawhub.ai`), independent of any server-sent hash.

The resolved release then enters the **same** acquire -> unpack -> map -> scan -> install pipeline every source shares, so a ClawHub skill lands **prompt-only at the `imported` trust tier** -- bundled MCP servers persist disabled, dynamic context forced off, text-only -- with `clawhub` recorded as its registry and `officialPublisher` in its provenance summary. The same honest limit applies as for every import: it adds instructions the agent may read, never the authority to run anything, and the import scan is a strong deterministic net, not a proof of safety.

## What Actually Lands: Text-Only Import

An import keeps **only UTF-8 text files**. Everything that could carry an execution vector is dropped before anything is written, and each drop is logged with a warning naming the exact trigger:

* Anything under a `scripts/` directory
* Any entry that arrived with a Unix executable bit
* Any file with a known executable, script, or compiled-binary extension
* Any file that is not valid UTF-8 text

An imported skill is therefore **prompt-only**: the SKILL.md instructions and its text reference files, never a shipped executable. A skill that bundled a helper script installs without that script, and the warning tells you which files were left behind.

## The Staged Pipeline

Every import -- regardless of source -- passes through the same ordered stages. A rejection at any stage is **atomic**: the staging area is removed and **zero** files reach your live skills directory.

<Steps>
  <Step title="Acquire">
    Fetch the bytes. Archive URLs go through the SSRF guard with a compressed-size cap; GitHub directories are walked through the fixed-host Contents API.
  </Step>

  <Step title="Unpack (bounded)">
    Archives are unpacked by in-house readers that reject absolute paths, `..` traversal segments, symlink and hardlink entries, and over-depth paths, while stream-counting uncompressed bytes against a total cap. The [size caps](#configuration) are operator-dialable.
  </Step>

  <Step title="Map the manifest">
    A skill authored for another ecosystem -- or in a legacy Comis form -- is converged onto the spec-pure `SKILL.md` shape. Fields with no internal home are dropped with a warning naming each key; an executable-entrypoint declaration is never carried across. See the [manifest mapping reference](/skills/manifest#foreign-frontmatter-mapping).
  </Step>

  <Step title="Scan (fail-closed, pre-write)">
    The content scanner runs on the SKILL.md body **and every kept text file** before anything is written. Any CRITICAL finding rejects the whole import. This scan is unconditional -- it does not consult the load-time `contentScanning` toggles. See [Security Scanning](/skills/security-scanning#import-time-scanning).
  </Step>

  <Step title="Install + pin provenance">
    Only the kept text files move into the live skills directory, under a serialized lock, and a provenance record is pinned recording the source, a content hash over the installed files, and the scan verdict. The install is stamped the `imported` trust tier.
  </Step>
</Steps>

## The Imported Trust Tier

A bundled skill that ships with Comis is platform-trusted. An **imported** skill is not -- and the tier has teeth that make that distinction real, not cosmetic:

* **Prompt-only.** No executables land (see above), so there is nothing to run directly.
* **Dynamic context is off.** Shell-command execution inside a skill body (`enableDynamicContext`) is forced off for an imported skill regardless of the global setting -- an imported skill can never opt itself into running commands during context assembly.
* **Bundled MCP servers persist disabled.** If an imported skill declares MCP servers, those entries are written with `enabled: false` and are **never auto-connected** at install. You opt in per server, and each connect re-runs the supply-chain (OSV) check at the connect site. There is intentionally no "auto-connect bundled MCP" configuration knob.
* **The model sees the tier.** A listed imported skill carries `source: "imported"` and a content-free provenance summary (acquisition channel, hash prefix, import timestamp), so both operators and the agent can tell a platform skill from an imported one.

The durable discriminator behind all of this is the provenance store (`<dataDir>/skill-provenance.json`, owner-only). Because the imported skills directory is the same one bundled skills seed into, the provenance record is the **only** thing that marks a skill imported -- and it is advisory downward only: a record can demote a skill to the imported tier, but its absence never elevates one.

## What the Scan Proves -- and What It Does Not

The import-time scan is a **strong, deterministic safety net**: it runs fail-closed on all staged text before any write, catches six categories of known-dangerous patterns, and cannot be silently disabled for imports. It meaningfully raises the bar against a hostile skill.

<Warning>
  The scanner is a strong deterministic net, **not a proof of safety.** It matches known-dangerous patterns; it cannot prove that a kept text file is benign. A prompt-only skill body is still natural-language text that can *describe* commands, argue for an action, or try to steer an agent toward one.

  That is why import trust and execution authority are kept separate: **an exec-capable agent remains governed by its tool policy, the approval gates, and the OS-level sandbox -- never by a skill's trust tier.** Importing a skill does not grant it the ability to run anything; it only adds instructions the agent may read. Scope your agents' tools with the same care whether a skill was authored in-house or imported.
</Warning>

This is defense in depth: the scan is one layer, and the tool policy plus sandbox are the layers that actually bound what an agent can do with what a skill tells it.

## Re-Importing and Updates

Re-importing a name that is already under provenance re-runs the full pipeline (the scan always runs again) and compares the recomputed content hash:

* **Identical hash** -- an idempotent no-op.
* **Divergent hash** -- refuses unless you pass `--confirm`, which swaps in the new content and re-pins the record.
* **A name with no provenance record, or whose record names a different source** -- refuses flatly. This is **not** overridable by `--confirm`; delete the existing skill first, then import.

<Note>
  There is no `force` override for import. `--confirm` narrowly acknowledges a content change on a re-import of the **same** source -- it never overwrites an unrelated or platform-trusted skill of the same name. Editing an imported skill locally re-pins its hash and marks it locally modified, so the change is visible rather than silently diverging from the pinned content.
</Note>

## Migrating Skills Imported Before the Pipeline

Provenance is **forward-only**. A skill installed by an older import path -- before the staged pipeline existed -- has no provenance record, so it keeps its path-derived source and is not retroactively stamped `imported`. There is deliberately no heuristic backfill: guessing a trust tier from a directory on disk is exactly the mistake the provenance store exists to prevent.

To bring such a skill under provenance, **delete it and re-import it** through the pipeline. The re-import scans it, records provenance, and stamps the imported tier -- the same sanctioned path as any first import.

## Configuration

The bounded-unpack caps live under each agent's `skills.import` block. The defaults are conservative; raise them only if you knowingly import large skills. See [`skills.import` in the config reference](/reference/config-yaml#skills-agents-skills).

| Cap                         | Default                                              |
| --------------------------- | ---------------------------------------------------- |
| `maxArchiveBytes`           | 8 MiB (compressed archive accepted for fetch/decode) |
| `maxTotalUncompressedBytes` | 64 MiB (stream-counted mid-inflate)                  |
| `maxFileCount`              | 200 entries                                          |
| `maxFileBytes`              | 4 MiB per unpacked file                              |
| `maxPathDepth`              | 10                                                   |

<Warning>
  The `documentation.skillsMarketplaceUrl` setting is unrelated to importing. It is only a link injected into the agent's system prompt as a hint -- it is **not** an allowlist and it does not gate, restrict, or authorize what `skills.import` will fetch. Import safety comes from the pipeline (SSRF-guarded fetch, bounded unpack, fail-closed scan, imported tier), not from that URL.
</Warning>

<CardGroup cols={2}>
  <Card title="Security Scanning" icon="shield-check" href="/skills/security-scanning">
    The six-category scanner that runs fail-closed at import time
  </Card>

  <Card title="Skill Manifest" icon="file-code" href="/skills/manifest">
    The SKILL.md format and the foreign-frontmatter mapping table
  </Card>

  <Card title="MCP Integration" icon="plug" href="/skills/mcp">
    How bundled MCP servers work -- and why imported ones start disabled
  </Card>

  <Card title="Tool Policy" icon="shield-halved" href="/skills/tool-policy">
    Bound what any agent can do, whatever a skill tells it
  </Card>
</CardGroup>
