Constant Time Analysis
Detect timing side-channel vulnerabilities in your cryptographic code. This analysis is crucial when implementing or reviewing any function that handles secrets, like encryption keys or authentication tokens, helping prevent information leaks from execution time variations.
Installation
This skill is self-contained. Copy the SKILL.md below directly into your project to get started.
.claude/skills/constant-time-analysis/SKILL.md # Claude Code
.cursor/skills/constant-time-analysis/SKILL.md # CursorOr install as a personal skill (available across all your projects):
~/.claude/skills/constant-time-analysis/SKILL.mdYou can also install using the skills CLI:
npx skills add trailofbits/skills --skill constant-time-analysisRequires Node.js 18+.
SKILL.md
---
name: constant-time-analysis
description: Detects timing side-channel vulnerabilities in cryptographic code. Use when implementing or reviewing crypto code, encountering division on secrets, secret-dependent branches, or constant-time programming questions in C, C++, Go, Rust, Swift, Java, Kotlin, C#, PHP, JavaScript, TypeScript, Python, or Ruby.
---
# Constant-Time Analysis
Analyze cryptographic code to detect operations that leak secret data through execution timing variations.
## When to Use
```text
User writing crypto code? ──yes──> Use this skill
│
no
│
v
User asking about timing attacks? ──yes──> Use this skill
│
no
│
v
Code handles secret keys/tokens? ──yes──> Use this skill
│
no
│
v
Skip this skill
```
**Concrete triggers:**
- User implements signature, encryption, or key derivation
- Code contains `/` or `%` operators on secret-derived values
- User mentions "constant-time", "timing attack", "side-channel", "KyberSlash"
- Reviewing functions named `sign`, `verify`, `encrypt`, `decrypt`, `derive_key`
## When NOT to Use
- Non-cryptographic code (business logic, UI, etc.)
- Public data processing where timing leaks don't matter
- Code that doesn't handle secrets, keys, or authentication tokens
- High-level API usage where timing is handled by the library
## Language Selection
Based on the file extension or language context, refer to the appropriate guide:
| Language | File Extensions | Guide |
| ---------- | --------------------------------- | -------------------------------------------------------- |
| C, C++ | `.c`, `.h`, `.cpp`, `.cc`, `.hpp` | [references/compiled.md](references/compiled.md) |
| Go | `.go` | [references/compiled.md](references/compiled.md) |
| Rust | `.rs` | [references/compiled.md](references/compiled.md) |
| Swift | `.swift` | [references/swift.md ([source](https://raw.githubusercontent.com/trailofbits/skills/main/plugins/constant-time-analysis/skills/constant-time-analysis/references/swift.md))](references/swift.md) |
| Java | `.java` | [references/vm-compiled.md ([source](https://raw.githubusercontent.com/trailofbits/skills/main/plugins/constant-time-analysis/skills/constant-time-analysis/references/vm-compiled.md))](references/vm-compiled.md) |
| Kotlin | `.kt`, `.kts` | [references/kotlin.md ([source](https://raw.githubusercontent.com/trailofbits/skills/main/plugins/constant-time-analysis/skills/constant-time-analysis/references/kotlin.md))](references/kotlin.md) |
| C# | `.cs` | [references/vm-compiled.md](references/vm-compiled.md) |
| PHP | `.php` | [references/php.md](references/php.md) |
| JavaScript | `.js`, `.mjs`, `.cjs` | [references/javascript.md](references/javascript.md) |
| TypeScript | `.ts`, `.tsx` | [references/javascript.md](references/javascript.md) |
| Python | `.py` | [references/python.md ([source](https://raw.githubusercontent.com/trailofbits/skills/main/plugins/constant-time-analysis/skills/constant-time-analysis/references/python.md))](references/python.md) |
| Ruby | `.rb` | [references/ruby.md ([source](https://raw.githubusercontent.com/trailofbits/skills/main/plugins/constant-time-analysis/skills/constant-time-analysis/references/ruby.md))](references/ruby.md) |
## Quick Start
```bash
# Analyze any supported file type
uv run {baseDir}/ct_analyzer/analyzer.py <source_file>
# Include conditional branch warnings
uv run {baseDir}/ct_analyzer/analyzer.py --warnings <source_file>
# Filter to specific functions
uv run {baseDir}/ct_analyzer/analyzer.py --func 'sign|verify' <source_file>
# JSON output for CI
uv run {baseDir}/ct_analyzer/analyzer.py --json <source_file>
```
### Native Compiled Languages Only (C, C++, Go, Rust)
```bash
# Cross-architecture testing (RECOMMENDED)
uv run {baseDir}/ct_analyzer/analyzer.py --arch x86_64 crypto.c
uv run {baseDir}/ct_analyzer/analyzer.py --arch arm64 crypto.c
# Multiple optimization levels
uv run {baseDir}/ct_analyzer/analyzer.py --opt-level O0 crypto.c
uv run {baseDir}/ct_analyzer/analyzer.py --opt-level O3 crypto.c
```
### VM-Compiled Languages (Java, Kotlin, C#)
```bash
# Analyze Java bytecode
uv run {baseDir}/ct_analyzer/analyzer.py CryptoUtils.java
# Analyze Kotlin bytecode (Android/JVM)
uv run {baseDir}/ct_analyzer/analyzer.py CryptoUtils.kt
# Analyze C# IL
uv run {baseDir}/ct_analyzer/analyzer.py CryptoUtils.cs
```
Note: Java, Kotlin, and C# compile to bytecode (JVM/CIL) that runs on a virtual machine with JIT compilation. The analyzer examines the bytecode directly, not the JIT-compiled native code. The `--arch` and `--opt-level` flags do not apply to these languages.
### Swift (iOS/macOS)
```bash
# Analyze Swift for native architecture
uv run {baseDir}/ct_analyzer/analyzer.py crypto.swift
# Analyze for specific architecture (iOS devices)
uv run {baseDir}/ct_analyzer/analyzer.py --arch arm64 crypto.swift
# Analyze with different optimization levels
uv run {baseDir}/ct_analyzer/analyzer.py --opt-level O0 crypto.swift
```
Note: Swift compiles to native code like C/C++/Go/Rust, so it uses assembly-level analysis and supports `--arch` and `--opt-level` flags.
### Prerequisites
| Language | Requirements |
| ---------------------- | --------------------------------------------------------- |
| C, C++, Go, Rust | Compiler in PATH (`gcc`/`clang`, `go`, `rustc`) |
| Swift | Xcode or Swift toolchain (`swiftc` in PATH) |
| Java | JDK with `javac` and `javap` in PATH |
| Kotlin | Kotlin compiler (`kotlinc`) + JDK (`javap`) in PATH |
| C# | .NET SDK + `ilspycmd` (`dotnet tool install -g ilspycmd`) |
| PHP | PHP with VLD extension or OPcache |
| JavaScript/TypeScript | Node.js in PATH |
| Python | Python 3.x in PATH |
| Ruby | Ruby with `--dump=insns` support |
**macOS users**: Homebrew installs Java and .NET as "keg-only". You must add them to your PATH:
```bash
# For Java (add to ~/.zshrc)
export PATH="/opt/homebrew/opt/openjdk@21/bin:$PATH"
# For .NET tools (add to ~/.zshrc)
export PATH="$HOME/.dotnet/tools:$PATH"
```
See [references/vm-compiled.md](references/vm-compiled.md) for detailed setup instructions and troubleshooting.
## Quick Reference
| Problem | Detection | Fix |
| ---------------------- | ------------------------------- | -------------------------------------------- |
| Division on secrets | DIV, IDIV, SDIV, UDIV | Barrett reduction or multiply-by-inverse |
| Branch on secrets | JE, JNE, BEQ, BNE | Constant-time selection (cmov, bit masking) |
| Secret comparison | Early-exit memcmp | Use `crypto/subtle` or constant-time compare |
| Weak RNG | rand(), mt_rand, Math.random | Use crypto-secure RNG |
| Table lookup by secret | Array subscript on secret index | Bit-sliced lookups |
## Interpreting Results
**PASSED** - No variable-time operations detected.
**FAILED** - Dangerous instructions found. Example:
```text
[ERROR] SDIV
Function: decompose_vulnerable
Reason: SDIV has early termination optimization; execution time depends on operand values
```
## Verifying Results (Avoiding False Positives)
**CRITICAL**: Not every flagged operation is a vulnerability. The tool has no data flow analysis - it flags ALL potentially dangerous operations regardless of whether they involve secrets.
For each flagged violation, ask: **Does this operation's input depend on secret data?**
1. **Identify the secret inputs** to the function (private keys, plaintext, signatures, tokens)
2. **Trace data flow** from the flagged instruction back to inputs
3. **Common false positive patterns**:
```c
// FALSE POSITIVE: Division uses public constant, not secret
int num_blocks = data_len / 16; // data_len is length, not content
// TRUE POSITIVE: Division involves secret-derived value
int32_t q = secret_coef / GAMMA2; // secret_coef from private key
```
4. **Document your analysis** for each flagged item
### Quick Triage Questions
| Question | If Yes | If No |
| ------------------------------------------------- | --------------------- | --------------------- |
| Is the operand a compile-time constant? | Likely false positive | Continue |
| Is the operand a public parameter (length, count)?| Likely false positive | Continue |
| Is the operand derived from key/plaintext/secret? | **TRUE POSITIVE** | Likely false positive |
| Can an attacker influence the operand value? | **TRUE POSITIVE** | Likely false positive |
## Limitations
1. **Static Analysis Only**: Analyzes assembly/bytecode, not runtime behavior. Cannot detect cache timing or microarchitectural side-channels.
2. **No Data Flow Analysis**: Flags all dangerous operations regardless of whether they process secrets. Manual review required.
3. **Compiler/Runtime Variations**: Different compilers, optimization levels, and runtime versions may produce different output.
## Real-World Impact
- **KyberSlash (2023)**: Division instructions in post-quantum ML-KEM implementations allowed key recovery
- **Lucky Thirteen (2013)**: Timing differences in CBC padding validation enabled plaintext recovery
- **RSA Timing Attacks**: Early implementations leaked private key bits through division timing
## References
- [Cryptocoding Guidelines](https://github.com/veorq/cryptocoding) - Defensive coding for crypto
- [KyberSlash](https://kyberslash.cr.yp.to/) - Division timing in post-quantum crypto
- [BearSSL Constant-Time](https://www.bearssl.org/constanttime.html) - Practical constant-time techniques
---
## Companion Files
The following reference files are included for convenience:
### references/compiled.md
# Constant-Time Analysis: Compiled Languages
Analysis guidance for C, C++, Go, and Rust. These languages compile to native assembly, where timing side-channels are detected by scanning for variable-time CPU instructions.
## Running the Analyzer
```bash
# C/C++ (default: clang, native architecture)
uv run {baseDir}/ct_analyzer/analyzer.py crypto.c
# Go
uv run {baseDir}/ct_analyzer/analyzer.py crypto.go
# Rust
uv run {baseDir}/ct_analyzer/analyzer.py crypto.rs
# Cross-architecture testing (RECOMMENDED)
uv run {baseDir}/ct_analyzer/analyzer.py --arch x86_64 crypto.c
uv run {baseDir}/ct_analyzer/analyzer.py --arch arm64 crypto.c
# Multiple optimization levels
uv run {baseDir}/ct_analyzer/analyzer.py --opt-level O0 crypto.c
uv run {baseDir}/ct_analyzer/analyzer.py --opt-level O3 crypto.c
# Include conditional branch warnings
uv run {baseDir}/ct_analyzer/analyzer.py --warnings crypto.c
# Filter to specific functions
uv run {baseDir}/ct_analyzer/analyzer.py --func 'sign|verify|decrypt' crypto.c
# CI-friendly JSON output
uv run {baseDir}/ct_analyzer/analyzer.py --json crypto.c
```
## Supported Compilers
| Language | Compiler | Flag |
|----------|----------|------|
| C/C++ | gcc | `--compiler gcc` |
| C/C++ | clang (default) | `--compiler clang` |
| Go | go | `--compiler go` |
| Rust | rustc | `--compiler rustc` |
## Supported Architectures
x86_64, arm64, arm, riscv64, ppc64le, s390x, i386
## Dangerous Instructions by Architecture
| Architecture | Division | Floating-Point |
|-------------|----------|----------------|
| x86_64 | DIV, IDIV, DIVQ, IDIVQ | DIVSS, DIVSD, SQRTSS, SQRTSD |
| ARM64 | UDIV, SDIV | FDIV, FSQRT |
| ARM | UDIV, SDIV | VDIV, VSQRT |
| RISC-V | DIV, DIVU, REM, REMU | FDIV.S, FDIV.D, FSQRT |
| PowerPC | DIVW, DIVD | FDIV, FSQRT |
| s390x | D, DR, DL, DLG, DSG | DDB, SQDB |
## Constant-Time Patterns
### Replace Division
```c
// VULNERABLE: Compiler emits DIV instruction
int32_t q = a / divisor;
// SAFE: Barrett reduction (precompute mu = ceil(2^32 / divisor))
uint32_t q = (uint32_t)(((uint64_t)a * mu) >> 32);
```
### Replace Branches
```c
// VULNERABLE: Branch timing reveals secret
if (secret) { result = a; } else { result = b; }
// SAFE: Constant-time selection
uint32_t mask = -(uint32_t)(secret != 0);
result = (a & mask) | (b & ~mask);
```
### Replace Comparisons
```c
// VULNERABLE: memcmp returns early on mismatch
if (memcmp(a, b, len) == 0) { ... }
// SAFE: Constant-time comparison
if (CRYPTO_memcmp(a, b, len) == 0) { ... } // OpenSSL
if (subtle.ConstantTimeCompare(a, b) == 1) { ... } // Go
```
## Common Mistakes
1. **Testing only one optimization level** - Compilers make different decisions at O0 vs O3. A clean O2 build may have divisions at O0.
2. **Testing only one architecture** - ARM and x86 have different division behavior. Test your deployment targets.
3. **Ignoring warnings** - Conditional branches on secrets are exploitable. Use `--warnings` and review each branch.
4. **Assuming the tool catches everything** - This tool detects instruction-level issues only. It cannot detect:
- Cache timing from memory access patterns
- Microarchitectural attacks (Spectre, etc.)
- Whether flagged code actually processes secrets
5. **Fixing symptoms, not causes** - If compiler introduces division, understand why. Sometimes the algorithm itself needs redesign.
## Go-Specific Notes
Go compiles to native code, so the analyzer builds a binary and disassembles it using `go tool objdump`. The analyzer:
- Sets `CGO_ENABLED=0` for pure Go analysis
- Supports cross-compilation via `GOARCH` environment variable
- Uses `-N -l` gcflags for O0 (disable optimizations)
## Rust-Specific Notes
Rust uses `rustc --emit=asm` for assembly generation. The analyzer:
- Maps optimization levels to rustc's `-C opt-level` flag
- Supports cross-compilation via `--target` flag
- Analyzes the emitted assembly for timing-unsafe instructions
## CI Integration
```yaml
- name: Check constant-time properties
run: |
uv run ct_analyzer/analyzer.py --json src/crypto/*.c
# Exit code 1 = violations found
```
### references/php.md
# Constant-Time Analysis: PHP
Analysis guidance for PHP scripts. Uses the VLD extension or OPcache debug output to analyze Zend opcodes.
## Prerequisites
### Installing VLD Extension
The VLD (Vulcan Logic Dumper) extension is required for detailed opcode analysis. OPcache fallback is available but provides less detail.
**Option 1: PECL Install (Recommended)**
```bash
# Query latest version from PECL
VLD_VERSION=$(curl -s https://pecl.php.net/package/vld | grep -oP 'vld-\K[0-9.]+(?=\.tgz)' | head -1)
echo "Latest VLD version: $VLD_VERSION"
# Install via PECL channel URL (avoids version detection issues)
pecl install channel://pecl.php.net/vld-${VLD_VERSION}
# Or if above fails, install with explicit channel:
pecl install https://pecl.php.net/get/vld-${VLD_VERSION}.tgz
```
**Option 2: Build from Source**
```bash
# Clone and build from GitHub
git clone https://github.com/derickr/vld.git
cd vld
phpize
./configure
make
sudo make install
# Add to php.ini
echo "extension=vld.so" | sudo tee -a $(php --ini | grep "Loaded Configuration" | cut -d: -f2 | tr -d ' ')
```
**Verify Installation**
```bash
php -m | grep -i vld
# Should output: vld
```
### macOS with Homebrew PHP
```bash
# Homebrew PHP may need manual extension directory setup
PHP_EXT_DIR=$(php -i | grep extension_dir | awk '{print $3}')
echo "PHP extension directory: $PHP_EXT_DIR"
# After building VLD, copy the extension
sudo cp modules/vld.so "$PHP_EXT_DIR/"
```
## Running the Analyzer
```bash
# Analyze PHP file
uv run {baseDir}/ct_analyzer/analyzer.py crypto.php
# Include warning-level violations
uv run {baseDir}/ct_analyzer/analyzer.py --warnings crypto.php
# Filter to specific functions
uv run {baseDir}/ct_analyzer/analyzer.py --func 'encrypt|decrypt' crypto.php
# JSON output for CI
uv run {baseDir}/ct_analyzer/analyzer.py --json crypto.php
```
## Dangerous Operations
### Opcodes (Errors)
| Opcode | Issue |
|--------|-------|
| DIV | Variable-time execution based on operand values |
| MOD | Variable-time execution based on operand values |
| POW | Variable-time execution |
### Functions (Errors)
| Function | Issue | Safe Alternative |
|----------|-------|------------------|
| `chr()` | Table lookup indexed by secret data | `pack('C', $int)` |
| `ord()` | Table lookup indexed by secret data | `unpack('C', $char)[1]` |
| `bin2hex()` | Table lookups indexed on secret data | Custom constant-time implementation |
| `hex2bin()` | Table lookups indexed on secret data | Custom constant-time implementation |
| `base64_encode()` | Table lookups indexed on secret data | Custom constant-time implementation |
| `base64_decode()` | Table lookups indexed on secret data | Custom constant-time implementation |
| `rand()` | Predictable | `random_int()` |
| `mt_rand()` | Predictable | `random_int()` |
| `array_rand()` | Uses mt_rand internally | `random_int()` |
| `uniqid()` | Predictable | `random_bytes()` |
| `shuffle()` | Uses mt_rand internally | Fisher-Yates with `random_int()` |
### Functions (Warnings)
| Function | Issue | Safe Alternative |
|----------|-------|------------------|
| `strcmp()` | Variable-time | `hash_equals()` |
| `strcasecmp()` | Variable-time | `hash_equals()` |
| `strncmp()` | Variable-time | `hash_equals()` |
| `substr_compare()` | Variable-time | `hash_equals()` |
| `serialize()` | Variable-length output | Fixed-length output |
| `json_encode()` | Variable-length output | Fixed-length output |
## Safe Patterns
### String Comparison
```php
// VULNERABLE: Early exit on mismatch
if ($user_token === $stored_token) { ... }
// SAFE: Constant-time comparison
if (hash_equals($stored_token, $user_token)) { ... }
```
### Random Number Generation
```php
// VULNERABLE: Predictable
$token = bin2hex(random_bytes(16)); // OK - random_bytes is secure
$index = mt_rand(0, count($array) - 1); // VULNERABLE
// SAFE: Cryptographically secure
$token = bin2hex(random_bytes(16));
$index = random_int(0, count($array) - 1);
```
### Character Operations
```php
// VULNERABLE: Table lookup timing
$byte = ord($secret_char);
$char = chr($secret_byte);
// SAFE: No table lookup
$byte = unpack('C', $secret_char)[1];
$char = pack('C', $secret_byte);
```
## Troubleshooting
### VLD Not Loading
```bash
# Check if extension is enabled
php -i | grep vld
# Check for loading errors
php -d display_errors=1 -d vld.active=1 -r "echo 'test';" 2>&1
# Common issue: wrong extension directory
php -i | grep extension_dir
ls $(php -r "echo ini_get('extension_dir');") | grep vld
```
### OPcache Fallback
If VLD is unavailable, the analyzer falls back to OPcache debug output:
```bash
# Manually test OPcache output
php -d opcache.enable_cli=1 -d opcache.opt_debug_level=0x10000 crypto.php 2>&1
```
OPcache provides less detailed output than VLD but still detects division/modulo opcodes.
### references/javascript.md
# Constant-Time Analysis: JavaScript and TypeScript
Analysis guidance for JavaScript and TypeScript. Uses V8 bytecode output from Node.js to detect timing-unsafe operations.
## Prerequisites
- **Node.js** (v14+) - for JavaScript analysis
- **TypeScript compiler** (tsc) - for TypeScript files (optional, uses npx fallback)
## Running the Analyzer
```bash
# Analyze JavaScript
uv run {baseDir}/ct_analyzer/analyzer.py crypto.js
# Analyze TypeScript (transpiles first)
uv run {baseDir}/ct_analyzer/analyzer.py crypto.ts
# Include warning-level violations
uv run {baseDir}/ct_analyzer/analyzer.py --warnings crypto.js
# Filter to specific functions
uv run {baseDir}/ct_analyzer/analyzer.py --func 'encrypt|sign' crypto.js
# JSON output for CI
uv run {baseDir}/ct_analyzer/analyzer.py --json crypto.js
```
## Dangerous Operations
### Bytecodes (Errors)
| Bytecode | Issue |
|----------|-------|
| Div | Variable-time execution based on operand values |
| Mod | Variable-time execution based on operand values |
| DivSmi | Division by small integer has variable-time execution |
| ModSmi | Modulo by small integer has variable-time execution |
### Functions (Errors)
| Function | Issue | Safe Alternative |
|----------|-------|------------------|
| `Math.sqrt()` | Variable latency based on operand values | Avoid in crypto |
| `Math.pow()` | Variable latency based on operand values | Avoid in crypto |
| `Math.random()` | Predictable | `crypto.getRandomValues()` |
| `eval()` | Unpredictable timing | Avoid entirely |
### Functions (Warnings)
| Function | Issue | Safe Alternative |
|----------|-------|------------------|
| `===` (strings) | Early-terminating | `crypto.timingSafeEqual()` |
| `indexOf()` | Early-terminating | Constant-time search |
| `includes()` | Early-terminating | Constant-time search |
| `startsWith()` | Early-terminating | `crypto.timingSafeEqual()` on prefix |
| `endsWith()` | Early-terminating | `crypto.timingSafeEqual()` on suffix |
| `JSON.stringify()` | Variable-length output | Fixed-length padding |
| `JSON.parse()` | Variable-time based on input | Fixed-length input |
| `btoa()` / `atob()` | Variable-length output | Fixed-length padding |
## Safe Patterns
### String Comparison (Node.js)
```javascript
// VULNERABLE: Early exit on mismatch
if (userToken === storedToken) { ... }
// SAFE: Constant-time comparison (Node.js)
const crypto = require('crypto');
if (crypto.timingSafeEqual(Buffer.from(userToken), Buffer.from(storedToken))) { ... }
```
### Random Number Generation
```javascript
// VULNERABLE: Predictable
const token = Math.random().toString(36);
// SAFE: Cryptographically secure (Node.js)
const crypto = require('crypto');
const token = crypto.randomBytes(16).toString('hex');
// SAFE: Browser
const array = new Uint8Array(16);
crypto.getRandomValues(array);
```
### Division Operations
```javascript
// VULNERABLE: Division has variable timing
const quotient = secret / divisor;
// SAFE: Use multiplication by inverse (if divisor is constant)
// Precompute: inverse = 1/divisor as fixed-point
const quotient = Math.floor(secret * inverse);
```
## TypeScript Notes
The analyzer:
1. Looks for `tsconfig.json` in parent directories
2. Transpiles TypeScript to JavaScript in a temp directory
3. Analyzes the transpiled JavaScript
4. Reports violations against the original TypeScript file
If tsc is not installed, the analyzer tries `npx tsc` as a fallback.
## Limitations
### V8 Bytecode Analysis
The analyzer uses `node --print-bytecode` to get V8 bytecode. This has limitations:
1. **JIT Compilation**: V8 may JIT-compile hot functions to native code with different timing characteristics
2. **Function Inlining**: Inlined functions may not appear in bytecode
3. **Deoptimization**: Code can be deoptimized back to bytecode
### Source-Level Detection
The analyzer also performs source-level pattern matching to detect:
- Division (`/`) and modulo (`%`) operators
- Dangerous function calls (`Math.random()`, etc.)
This catches issues that bytecode analysis might miss due to parsing limitations.
## Browser Considerations
The analyzer targets Node.js V8 bytecode. Browser JavaScript engines (SpiderMonkey, JavaScriptCore) have different bytecode formats and timing characteristics.
For browser-targeted code:
- The V8 analysis is still valuable as a baseline
- Consider additional testing in target browsers
- Use Web Crypto API for cryptographic operations
Originally by Trail of Bits, adapted here as an Agent Skills compatible SKILL.md.
This skill follows the Agent Skills open standard, supported by Claude Code, Cursor, Codex, Gemini CLI, and 20+ more editors.
Works with
Agent Skills format — supported by 20+ editors. Learn more