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

# Security Scanning

> What Comis checks when installing and loading custom skills

Comis inspects skill content at two points: an **install-time vetting gate** that runs before any file is written to disk, and a **load-time scan** each time a prompt skill is read into context. Both use the same six categories of dangerous patterns. This is a heuristic content filter, not malware analysis, code isolation, or a guarantee that an untrusted skill is safe.

## When Scanning Happens

<CardGroup cols={2}>
  <Card title="Install time" icon="shield-check">
    Runs on **every** file in the skill bundle before the first byte is written. Applies to all four install paths: `skills.create`, `skills.update`, `skills.upload`, and `skills.import`. A CRITICAL finding refuses the install with **zero files written**.
  </Card>

  <Card title="Load time" icon="file-import">
    Runs on the sanitized `SKILL.md` body each time the skill is loaded into context. Catches a skill edited directly on disk after installation.
  </Card>
</CardGroup>

The two differ in scope and in what an operator may change:

|                      | Install gate                        | Load-time scan                            |
| -------------------- | ----------------------------------- | ----------------------------------------- |
| Surface              | Every text file in the bundle       | The `SKILL.md` body only                  |
| Also checks          | Bundle structure, manifest validity | —                                         |
| Operator-disableable | **No**                              | Yes (`contentScanning.enabled`)           |
| On CRITICAL          | Install refused, nothing written    | Skill not loaded (when `blockOnCritical`) |

<Warning>
  `contentScanning.enabled: false` relaxes only the **load-time** scan. It does not disable the install gate — a deployment cannot configure its way into writing unvetted skill files.
</Warning>

## What Gets Scanned

The content scanner checks skill text for six categories of dangerous patterns. At install time it reads every text member of the bundle -- the `SKILL.md` body **and** support files such as `references/`, `scripts/`, `templates/`, and `assets/`. It does not scan or confine arbitrary code, MCP server executables, browser processes, or other integrations. Pattern matching can have false positives and false negatives, so operators must still review third-party skills and everything they instruct an agent to invoke.

<AccordionGroup>
  <Accordion title="Command Injection" icon="terminal">
    **Severity:** CRITICAL

    Detects attempts to run system commands through skill content. Attackers embed shell commands hoping the agent will execute them blindly.

    **Examples caught:**

    * `$(command)` -- subshell execution
    * Backtick command substitution
    * `eval()` calls
    * Piped commands like `curl | bash`
  </Accordion>

  <Accordion title="Environment Harvesting" icon="key">
    **Severity:** WARN

    Detects attempts to read sensitive environment variables where API keys, passwords, and secrets are often stored. Findings are logged but do not block skill loading by default.

    **Examples caught:**

    * `printenv` commands
    * Reading `/proc/*/environ`
    * `env | grep` patterns that filter for specific secrets
  </Accordion>

  <Accordion title="Crypto Mining" icon="microchip">
    **Severity:** CRITICAL

    Detects cryptocurrency mining attempts that would hijack your server's computing power.

    **Examples caught:**

    * `stratum+tcp://` mining pool URLs
    * Known miner binary names
    * Mining pool domain references
  </Accordion>

  <Accordion title="Network Exfiltration" icon="globe">
    **Severity:** mixed (WARN for piped downloads, CRITICAL for reverse shells)

    Detects attempts to send your data to external servers without authorization.

    **Examples caught:**

    * `curl | sh` patterns that download and execute remote code (WARN)
    * `wget -O- | bash` download-and-run chains (WARN)
    * Reverse shell patterns that open a connection back to an attacker (CRITICAL)
  </Accordion>

  <Accordion title="Obfuscated Encoding" icon="eye-slash">
    **Severity:** mixed (WARN for long encoded blobs, CRITICAL for decode-and-execute chains)

    Detects suspiciously encoded content that may be hiding malicious instructions. Encoding is not always malicious, but large encoded blocks in a skill body are a red flag.

    **Examples caught:**

    * Unusually long base64-encoded strings (WARN)
    * Hex-encoded payloads (WARN)
    * `base64 -d | bash` decode-and-execute patterns (CRITICAL)
  </Accordion>

  <Accordion title="XML Breakout" icon="code">
    **Severity:** CRITICAL

    Detects attempts to escape the skill boundary and inject content directly into the agent's system prompt.

    **Examples caught:**

    * `</skill>` close tags that try to end the skill context early
    * `<system>` tags that attempt to hijack the prompt structure
  </Accordion>
</AccordionGroup>

## The Sanitization Pipeline

In addition to scanning for threats, every prompt skill body goes through a four-step sanitization pipeline before it reaches the agent. This pipeline runs automatically on all skills, regardless of scan results.

<Steps>
  <Step title="Strip HTML comments">
    Removes all `<!-- ... -->` comment blocks. HTML comments are invisible in rendered content, making them a common way to hide malicious instructions that humans would not notice during review.
  </Step>

  <Step title="Unicode normalization">
    Converts fancy characters to their standard forms using NFKC normalization. This prevents visual tricks where an attacker uses fullwidth or ligature characters that look identical to normal text but bypass pattern matching. For example, a fullwidth `ｅｖａｌ` looks like `eval` but would not match a simple text scan.
  </Step>

  <Step title="Strip invisible characters">
    Removes zero-width Unicode characters like zero-width spaces, zero-width joiners, and Unicode tag characters. These are completely invisible in text but can carry hidden payloads or break security boundaries.
  </Step>

  <Step title="Enforce size limit">
    Truncates the skill body at the configured maximum length (default: 20,000 characters). If the body exceeds this limit, the excess is removed and a `[TRUNCATED]` marker is appended. This prevents skills from overwhelming the agent's context window.
  </Step>
</Steps>

## Bundle Structure Checks

Beyond pattern matching, the install gate checks the **shape** of the bundle -- conditions a regular expression cannot see. These run first, so an oversized or malformed bundle is refused before any pattern matching happens.

Refused outright:

* **No `SKILL.md`** at the bundle root, or a manifest whose frontmatter does not parse or does not validate. Previously such a skill installed successfully and then silently failed to appear.
* **A non-prompt `type`.** Comis skills are prompt-only.
* **Unsafe member paths** -- absolute paths, `..` traversal, drive-letter prefixes, or excessive nesting depth.
* **Symlink or hardlink members.** A skill has no legitimate use for one.
* **Binary or executable members**, detected by file contents rather than by extension, so a payload named `notes.md` is still caught.
* **Bound breaches** -- too many files, too large a bundle, or too large a single file. See [Configuration](#configuration).

Logged as warnings, but installed:

* An executable bit on a member that is not a recognized script type.
* Deep (but permitted) directory nesting.
* A manifest matched only case-insensitively (`skill.md` rather than `SKILL.md`).

A bundle may ship a `.skillignore` file with gitignore-style patterns to exclude development artifacts from the structure checks. It never affects pattern scanning, and it cannot exclude `SKILL.md`.

## Frontmatter Normalization

Skills authored for other agent runtimes often spell manifest fields in kebab-case (`allowed-tools`, `argument-hint`). The install gate maps these onto their Comis equivalents so such skills install rather than failing validation.

Fields with no Comis equivalent are **dropped with a logged warning that names the key** -- never silently reinterpreted. Fields implying code execution (a script entrypoint, a `run` command, an install hook) are also dropped: the skill installs as its prompt body only, with a warning recording exactly what was discarded. If both spellings of a field are present, the Comis-native camelCase key wins and the other is reported.

## What Happens When a Threat is Found

At **install time** the outcome depends on both the findings and where the skill came from. Comis assigns every install a trust tier from the call itself -- never from anything the skill declares:

| Tier             | Assigned to                                                         |
| ---------------- | ------------------------------------------------------------------- |
| `first-party`    | skills the daemon seeds itself                                      |
| `operator`       | skills you author (`create`/`update`/`upload` as the default agent) |
| `community`      | any remote import                                                   |
| `agent-authored` | skills an agent writes at runtime                                   |

The tier and the verdict together decide what happens:

|                  | no findings | WARN only   | any CRITICAL |
| ---------------- | ----------- | ----------- | ------------ |
| `first-party`    | install     | install     | install      |
| `operator`       | install     | install     | **confirm**  |
| `community`      | install     | **confirm** | **refuse**   |
| `agent-authored` | install     | **confirm** | **refuse**   |

**Confirm** means the install is refused with a message naming every finding and its location, and you re-run the same call with `force: true` to acknowledge them. **Refuse** cannot be overridden: `force` upgrades a confirmation, never a refusal. A CRITICAL finding in a skill you did not write is not installable, by any flag.

The asymmetry is deliberate. A CRITICAL pattern in a skill you authored is a mistake worth stopping for, but it is your content and your judgement. The same pattern arriving from a stranger is not.

At **load time** the rule is simpler: a CRITICAL finding blocks the skill from loading when `contentScanning.blockOnCritical` is enabled (the default). The agent never sees the content, and a log entry records what was found.

**WARN findings** never block a load, and never refuse an install -- at most they ask for a confirmation. Review them and decide: obfuscated encoding, for example, may be legitimate, since some skills include base64 data for valid reasons.

<Info>
  Even if a prompt skill passes content scanning, it still goes through the sanitization pipeline. These controls reduce specific prompt-content risks, but they do not establish a process sandbox or prove that the skill's instructions are trustworthy.
</Info>

<Warning>
  The `permissions` and `allowedTools` fields in SKILL.md are currently parsed as
  manifest metadata; they are not an enforced per-skill runtime authorization
  boundary. Configure the agent's `toolPolicy` and the underlying tool-specific
  controls for actual enforcement. The default agent tool-policy profile is
  `full`, so narrow it before exposing an agent to untrusted content.
</Warning>

## Configuration

Content scanning is enabled by default. You can adjust its behavior in your configuration file:

```yaml theme={}
skills:
  contentScanning:
    enabled: true            # Load-time scanning (default: true)
    blockOnCritical: true    # Block loading on CRITICAL findings (default: true)
  installVetting:
    maxEntries: 200                # Members per bundle (default: 200)
    maxEntryBytes: 4194304         # Bytes per member, 4 MiB (default)
    maxBundleBytes: 33554432       # Bytes per bundle, 32 MiB (default)
    maxPathDepth: 10               # Member path depth (default: 10)
```

The `installVetting` block tunes the install gate's **bounds** only. There is no switch to turn the install gate off.

<Warning>
  Setting `contentScanning.enabled` to `false` or `blockOnCritical` to `false` removes the load-time layer. Only do this if you fully trust every skill that Comis will load, and you understand the risks. Neither setting affects the install gate.
</Warning>

## How Scanning Fits into the Security Stack

Prompt-skill scanning is one layer in the Comis [defense-in-depth](/security/defense-in-depth) security model:

1. **Install vetting** inspects every file in a skill bundle, plus its structure and manifest, before anything is written to disk (this page)
2. **Content scanning** flags known patterns before a prompt skill loads (this page)
3. **Sanitization** removes selected hidden or obfuscated content from prompt-skill bodies (this page)
4. **Agent tool policy** filters registered tools by the configured profile and allow/deny rules ([Tool Policy](/skills/tool-policy))
5. **Tool-specific validation** constrains the particular operation being requested
6. **Exec sandboxing**, when a supported provider is active, confines only child processes launched through ordinary `exec`; MCP servers, browsers, and in-process tools are outside it ([Sandbox](/security/sandbox))
7. **Completed-response scanning** can catch secret-shaped content before non-streaming delivery; already-emitted streaming deltas cannot be retracted

These layers have different scopes. Verify the controls that apply to the exact skill, tool, host, and delivery mode you deploy.

<CardGroup cols={2}>
  <Card title="Sandbox" icon="lock" href="/security/sandbox">
    Where process isolation applies, and where it does not
  </Card>

  <Card title="Defense in Depth" icon="shield" href="/security/defense-in-depth">
    All security layers explained
  </Card>

  <Card title="Manifest Reference" icon="file-code" href="/skills/manifest">
    Declaring skill permissions
  </Card>

  <Card title="Prompt Skills" icon="file-lines" href="/skills/prompt-skills">
    Creating custom prompt skills
  </Card>
</CardGroup>
