# How to Reject a Candidate Version in Distilly: Security Validation Guide

> Learn how to reject a candidate version in Distilly. Understand automatic rejection triggers and security validation for safer path patterns.

- Repository: [Tianyi Zhou/distilly](https://github.com/titanwings/distilly)
- Tags: security-validation-guide
- Published: 2026-09-10

---

**Distilly rejects candidate versions automatically when `validate_path_segment` or `resolve_contained_child` detect unsafe path patterns, outputting errors to stderr and aborting with exit code 1.**

Distilly (titanwings/distilly) is a skill storage management system designed to safeguard file system integrity. When you need to reject a candidate version in Distilly, the framework employs a multi-layered validation strategy that intercepts malicious or malformed version strings before they touch the disk. These protections are implemented in [`tools/version_manager.py`](https://github.com/titanwings/distilly/blob/main/tools/version_manager.py) and enforced across all version management operations.

## Understanding Distilly's Version Validation Architecture

The rejection mechanism operates through defensive programming patterns that validate inputs at three distinct boundaries. According to the titanwings/distilly source code, every version candidate undergoes rigorous path segment validation to prevent directory traversal, symlink escapes, and unauthorized file system access.

The validation hierarchy checks:
- **Skill slug safety** before processing
- **Version directory containment** during resolution  
- **Version string integrity** during backup and rollback operations

## The Three Security Checkpoints for Version Rejection

### Slug Validation via validate_path_segment

Before any version operation proceeds, Distilly validates the skill slug using `validate_path_segment`. Located at lines **94‑96** in [`tools/version_manager.py`](https://github.com/titanwings/distilly/blob/main/tools/version_manager.py), this check ensures the slug contains no path traversal sequences like `..` or directory separators.

If the slug contains illegal characters, the manager immediately rejects the candidate and terminates the operation, preventing the creation of or access to unsafe directory structures.

### Directory Containment via resolve_contained_child

The `resolve_contained_child` function guarantees that resolved version paths remain strictly within the skill's `versions` folder. As implemented in [`tools/version_manager.py`](https://github.com/titanwings/distilly/blob/main/tools/version_manager.py) at lines **84‑86** (for rollback) and **45‑47** (for listing), this function raises a `ValueError` if the version path would escape the intended container.

This containment check effectively rejects any candidate version that attempts to reference files outside the managed skill directory, blocking path traversal attacks at the resolution stage.

### Version String Sanitization

When creating backups or performing rollbacks, Distilly applies `validate_path_segment` directly to the version identifier. Lines **46‑48** (backup) and **102‑108** (rollback backup) in [`tools/version_manager.py`](https://github.com/titanwings/distilly/blob/main/tools/version_manager.py) enforce this validation, rejecting strings containing `..`, `/`, or other unsafe patterns that could manipulate the file system.

## Practical Examples of Rejecting Invalid Versions

### CLI Error Output for Unsafe Version Strings

When you attempt to back up a skill with a compromised version identifier, Distilly detects the violation and rejects the operation:

```bash

# Attempt to back up a skill with an unsafe version string

distilly version-manager \
  --action backup \
  --slug my-skill \
  --character colleague \
  --base-dir ./skill-storage

```

If the current [`meta.json`](https://github.com/titanwings/distilly/blob/main/meta.json) reports a version like `../evil`, the tool prints to stderr and exits:

```

error: version string contains unsafe path segment: ../evil

```

The process terminates with status **1**, rejecting the candidate version before any file operations occur.

### Blocking Directory Traversal in Rollbacks

Explicit attempts to escape the versions directory trigger immediate rejection:

```bash

# Attempting to roll back to a version outside the versions folder

distilly version-manager \
  --action rollback \
  --slug my-skill \
  --version ../../outside \
  --character colleague

```

The manager detects the traversal attempt and outputs:

```

error: version contains unsafe path segment: ../../outside

```

The function returns false, preventing the rollback operation from executing.

### Programmatic Version Validation

You can leverage Distilly's validation functions directly in Python scripts to reject candidates before processing:

```python
from pathlib import Path
from tools.version_manager import rollback
from tools.skill_schema import validate_path_segment

try:
    # Returns clean string or raises ValueError

    safe_version = validate_path_segment(
        "../../bad", 
        "candidate version"
    )
    success = rollback(
        Path("./skill-storage/colleague/my-skill"), 
        safe_version
    )
except ValueError as e:
    # Handle rejected candidate

    print(f"Rejected candidate version: {e}")
    exit(1)

```

## Summary

- **Multi-layer validation**: Distilly rejects candidate versions through `validate_path_segment` (lines 94‑96, 46‑48, 102‑108) and `resolve_contained_child` (lines 84‑86, 45‑47) in [`tools/version_manager.py`](https://github.com/titanwings/distilly/blob/main/tools/version_manager.py).
- **Path traversal prevention**: The system blocks `..`, `/`, and other unsafe patterns in both skill slugs and version identifiers.
- **Immediate termination**: Rejected operations print errors to **stderr** and exit with status **1**, ensuring no unsafe file system modifications occur.
- **Container enforcement**: `resolve_contained_child` guarantees version paths remain within the designated `versions` directory.

## Frequently Asked Questions

### What triggers a candidate version rejection in Distilly?

Distilly rejects candidate versions containing path traversal sequences (`..`), forward slashes, or other characters that could escape the intended directory structure. The `validate_path_segment` function raises a `ValueError` when it detects these patterns in either the skill slug or version string, causing the version manager to abort the operation immediately.

### Which functions handle version validation in the source code?

The primary validation functions are `validate_path_segment` (located in [`tools/skill_schema.py`](https://github.com/titanwings/distilly/blob/main/tools/skill_schema.py) and invoked throughout [`tools/version_manager.py`](https://github.com/titanwings/distilly/blob/main/tools/version_manager.py)) and `resolve_contained_child` (defined in [`tools/version_manager.py`](https://github.com/titanwings/distilly/blob/main/tools/version_manager.py)). These functions check path segments at lines 94‑96, 46‑48, and 102‑108, and resolve contained paths at lines 84‑86 and 45‑47 respectively.

### How does Distilly prevent directory traversal attacks?

Distilly prevents directory traversal through the `resolve_contained_child` function, which verifies that resolved paths remain strictly within the skill's `versions` folder. If a version string attempts to reference parent directories (e.g., `../../outside`), the function raises a `ValueError`, effectively rejecting the candidate before any file system access occurs.

### What exit code does Distilly return when rejecting a version?

When Distilly rejects a candidate version due to validation failures, the CLI tool prints an error message to **stderr** and exits with status **1**. This non-zero exit code allows shell scripts and CI/CD pipelines to detect and handle rejection events programmatically.