Files
trailofbits-skills/plugins/constant-time-analysis
Jonathan Hefner debfb29c8e Fix allowed-tools to use spec-compliant space-delimited strings (#139)
* Fix `allowed-tools` to use spec-compliant space-delimited strings

Per the agentskills.io specification, `allowed-tools` must be a single
string of space-delimited patterns, not a YAML list. Converted all 23
SKILL.md files from the `- Item` list format to the correct
`"Item1 Item2"` string format. Also updated the frontmatter examples in
CLAUDE.md and the workflow-skill-design skill template to match.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix remaining allowed-tools format in firebase-apk-scanner and workflow-skill-design docs

- Convert firebase-apk-scanner from comma-separated to space-delimited
- Update anti-patterns.md and tool-assignment-guide.md examples from YAML lists to space-delimited strings
- Remove unnecessary quotes from SKILL.md template placeholder

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Cover commands, new SKILL.md files, and fix template placeholder

Extends the previous spec-compliance fixes:

* Convert command frontmatter (commands/*.md) — per Claude Code
  docs, command files use the same frontmatter as skills, so the
  same space-delimited rule applies.
* Convert three SKILL.md files added since the original PR:
  mutation-testing, trailmark-structural, trailmark-summary.
* Fix the placeholder in the workflow-skill-design template.
  The previous "[minimum tools needed, space-delimited]" was YAML
  flow-sequence syntax, which parses as a list — the opposite of
  what the placeholder claims. Replaced with a concrete-looking
  space-delimited example plus a comment.

Zeroize-audit agent files still use `allowed-tools:` in YAML list
form. They are intentionally excluded: per the project's own docs
(workflow-skill-design references), agents declare tools with
`tools:` (not `allowed-tools:`). Fixing those requires changing
the field name as well as the format and is out of scope for this
PR.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* zeroize-audit agents: switch allowed-tools to tools

Subagents declare their tool allowlist via `tools:` (comma-separated),
not `allowed-tools:` — see Claude Code's subagent docs and this
repo's own designing-workflow-skills/SKILL.md:47:

> Skills use `allowed-tools:` in frontmatter. Agents use `tools:`
> in frontmatter.

Before this change, the zeroize-audit agents declared their tool list
under `allowed-tools:`, which Claude Code does not read for subagents.
The field was effectively a no-op; the spawned agents had no tool
restriction enforced.

Renames the field on all 11 agents to `tools:` and reformats the YAML
list as comma-separated to match the documented format and existing
agents elsewhere in the repo (e.g. function-analyzer.md,
spec-compliance-checker.md). Tool sets are unchanged.

Behavior change: tools now actually constrain what each spawned agent
can call. The lists are the ones the original author intended.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* skill-improver: convert command allowed-tools to space-delimited

The two command files in plugins/skill-improver/commands/ still used
the JSON flow-array format (`allowed-tools: ["..."]`), which the rest
of this PR converted everywhere else. Convert them to the spec-compliant
space-delimited string form for consistency.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Dan Guido <dan@trailofbits.com>
2026-04-28 19:50:30 -04:00
..

Constant-Time Analyzer (ct-analyzer)

A portable tool for detecting timing side-channel vulnerabilities in compiled cryptographic code. Analyzes assembly output from multiple compilers and architectures to detect instructions that could leak secret data through execution timing.

Background

Timing side-channel attacks exploit variations in execution time to extract secret information from cryptographic implementations. Common sources include:

  • Hardware division (DIV, IDIV): Execution time varies based on operand values
  • Floating-point operations (FDIV, FSQRT): Variable latency based on inputs
  • Conditional branches: Different execution paths have different timing

The infamous KyberSlash attack demonstrated how division instructions in post-quantum cryptographic implementations could be exploited to recover secret keys.

Features

  • Multi-language support: C, C++, Go, Rust, PHP, JavaScript, TypeScript, Python, Ruby
  • Multi-architecture support: x86_64, ARM64, ARM, RISC-V, PowerPC, s390x, i386
  • Multi-compiler support: GCC, Clang, Go compiler, Rustc
  • Scripting language support: PHP (VLD/opcache), JavaScript/TypeScript (V8 bytecode), Python (dis), Ruby (YARV)
  • Optimization-level testing: Test across O0-O3, Os, Oz
  • Multiple output formats: Text, JSON, GitHub Actions annotations
  • Cross-compilation: Analyze code for different target architectures

Quick Start

# Install
uv pip install -e .

# Analyze a C file
ct-analyzer crypto.c

Usage

Basic Analysis

ct-analyzer <source_file>

Options

Option Description
--arch, -a Target architecture (x86_64, arm64, arm, riscv64, ppc64le, s390x, i386)
--compiler, -c Compiler to use (gcc, clang, go, rustc)
--opt-level, -O Optimization level (O0, O1, O2, O3, Os, Oz) - default: O2
--warnings, -w Include conditional branch warnings
--func, -f Regex pattern to filter functions
--json Output JSON format
--github Output GitHub Actions annotations
--list-arch List supported architectures

Examples

# Test with different optimization levels
ct-analyzer --opt-level O0 crypto.c
ct-analyzer --opt-level O3 crypto.c

# Cross-compile for ARM64
ct-analyzer --arch arm64 crypto.c

# Include conditional branch warnings
ct-analyzer --warnings crypto.c

# Analyze specific functions
ct-analyzer --func 'decompose|sign' crypto.c

# JSON output for CI
ct-analyzer --json crypto.c

# Analyze Go code
ct-analyzer crypto.go

# Analyze Rust code
ct-analyzer crypto.rs

# Analyze PHP code (requires PHP with VLD extension or opcache)
ct-analyzer crypto.php

# Analyze TypeScript (transpiles to JS first)
ct-analyzer crypto.ts

# Analyze JavaScript (uses V8 bytecode analysis)
ct-analyzer crypto.js

# Analyze Python (uses dis module for bytecode disassembly)
ct-analyzer crypto.py

# Analyze Ruby (uses YARV instruction dump)
ct-analyzer crypto.rb

Detected Vulnerabilities

Error-Level (Must Fix)

Category x86_64 ARM64 RISC-V
Integer Division DIV, IDIV, DIVQ, IDIVQ UDIV, SDIV DIV, DIVU, REM, REMU
FP Division DIVSS, DIVSD, DIVPS, DIVPD FDIV FDIV.S, FDIV.D
Square Root SQRTSS, SQRTSD, SQRTPS, SQRTPD FSQRT FSQRT.S, FSQRT.D

Warning-Level (Review Needed)

Conditional branches that may leak timing if condition depends on secret data:

  • x86: JE, JNE, JZ, JNZ, JA, JB, JG, JL, etc.
  • ARM: BEQ, BNE, CBZ, CBNZ, TBZ, TBNZ
  • RISC-V: BEQ, BNE, BLT, BGE

Scripting Language Support

PHP Analysis

PHP analysis uses either the VLD extension (recommended) or opcache debug output:

Detected PHP Vulnerabilities:

Category Pattern Recommendation
Division ZEND_DIV, ZEND_MOD Use Barrett reduction
Cache timing chr(), ord() Use pack('C', $int) / unpack('C', $char)[1]
Table lookups bin2hex(), hex2bin(), base64_encode() Use constant-time alternatives
Array access FETCH_DIM_R (secret index) Use constant-time table lookup
Bit shifts ZEND_SL, ZEND_SR (secret amount) Mask shift amount
Variable encoding pack(), serialize(), json_encode() Use fixed-length output
Weak RNG rand(), mt_rand(), uniqid() Use random_int() / random_bytes()
String comparison strcmp(), === on secrets Use hash_equals()

Installation:

# Install VLD extension (recommended)
# Query latest version from PECL
VLD_VERSION=$(curl -s https://pecl.php.net/package/vld | grep -oP 'vld-\K[0-9.]+(?=\.tgz)' | head -1)
pecl install channel://pecl.php.net/vld-${VLD_VERSION}

# Or build from source (if PECL fails)
git clone https://github.com/derickr/vld.git && cd vld
phpize && ./configure && make && sudo make install

# Or use opcache (built-in, fallback)
# Enabled by default in PHP 7+

JavaScript/TypeScript Analysis

JavaScript analysis uses V8 bytecode via Node.js --print-bytecode. TypeScript files are automatically transpiled first.

Detected JS Vulnerabilities:

Category Pattern Recommendation
Division Div, Mod bytecodes Use constant-time multiply-shift
Array access LdaKeyedProperty (secret index) Use constant-time table lookup
Bit shifts ShiftLeft, ShiftRight (secret amount) Mask shift amount
Variable encoding TextEncoder, JSON.stringify(), btoa() Use fixed-length output
Weak RNG Math.random() Use crypto.getRandomValues() or crypto.randomBytes()
Variable latency Math.sqrt(), Math.pow() Avoid in crypto paths
String comparison === on secrets Use crypto.timingSafeEqual() (Node.js)
Early-exit search indexOf(), includes() Use constant-time comparison

Requirements:

# Node.js required
node --version

# TypeScript compiler (optional, for .ts files)
npm install -g typescript

Python Analysis

Python analysis uses the built-in dis module to analyze CPython bytecode.

Detected Python Vulnerabilities:

Category Pattern Recommendation
Division BINARY_OP 11 (/), BINARY_OP 6 (%) Use Barrett reduction or constant-time alternatives
Array access BINARY_SUBSCR (secret index) Use constant-time table lookup
Bit shifts BINARY_LSHIFT, BINARY_RSHIFT (secret amount) Mask shift amount
Variable encoding int.to_bytes(), json.dumps(), base64.b64encode() Use fixed-length output
Weak RNG random.random(), random.randint() Use secrets.token_bytes() / secrets.randbelow()
Variable latency math.sqrt(), math.pow() Avoid in crypto paths
String comparison == on secrets Use hmac.compare_digest()
Early-exit search .find(), .startswith() Use constant-time comparison

Requirements:

# Python 3.x required (built-in dis module)
python3 --version

Ruby Analysis

Ruby analysis uses YARV (Yet Another Ruby VM) bytecode via ruby --dump=insns.

Detected Ruby Vulnerabilities:

Category Pattern Recommendation
Division opt_div, opt_mod Use constant-time alternatives
Array access opt_aref (secret index) Use constant-time table lookup
Bit shifts opt_lshift, opt_rshift (secret amount) Mask shift amount
Variable encoding pack(), to_json(), Base64.encode64() Use fixed-length output
Weak RNG rand(), Random.new Use SecureRandom.random_bytes()
Variable latency Math.sqrt() Avoid in crypto paths
String comparison == on secrets Use Rack::Utils.secure_compare() or OpenSSL
Early-exit search .include?(), .start_with?() Use constant-time comparison

Requirements:

# Ruby required (YARV is standard since Ruby 1.9)
ruby --version

Example Output

============================================================
Constant-Time Analysis Report
============================================================
Source: decompose.c
Architecture: arm64
Compiler: clang
Optimization: O2
Functions analyzed: 4
Instructions analyzed: 88

VIOLATIONS FOUND:
----------------------------------------
[ERROR] SDIV
  Function: decompose_vulnerable
  Reason: SDIV has early termination optimization; execution time depends on operand values

[ERROR] SDIV
  Function: use_hint_vulnerable
  Reason: SDIV has early termination optimization; execution time depends on operand values

----------------------------------------
Result: FAILED
Errors: 2, Warnings: 0

Fixing Violations

Replace Division with Barrett Reduction

// VULNERABLE
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 with Constant-Time Selection

// VULNERABLE
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

// VULNERABLE
if (memcmp(a, b, len) == 0) { ... }

// SAFE: Use crypto/subtle or equivalent
if (subtle.ConstantTimeCompare(a, b) == 1) { ... }

Test Samples

The repository includes test samples demonstrating vulnerable and secure implementations:

  • ct_analyzer/tests/test_samples/decompose_vulnerable.c - Vulnerable C implementation
  • ct_analyzer/tests/test_samples/decompose_constant_time.c - Constant-time C implementation
  • ct_analyzer/tests/test_samples/decompose_vulnerable.go - Vulnerable Go implementation
  • ct_analyzer/tests/test_samples/decompose_vulnerable.rs - Vulnerable Rust implementation
  • ct_analyzer/tests/test_samples/vulnerable.php - Vulnerable PHP implementation
  • ct_analyzer/tests/test_samples/vulnerable.ts - Vulnerable TypeScript implementation
  • ct_analyzer/tests/test_samples/vulnerable.py - Vulnerable Python implementation
  • ct_analyzer/tests/test_samples/vulnerable.rb - Vulnerable Ruby implementation

These implement the Decompose and UseHint algorithms from ML-DSA (FIPS-204) as test cases.

CI Integration

GitHub Actions

name: Constant-Time Check

on: [push, pull_request]

jobs:
  ct-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'

      - name: Install dependencies
        run: |
          uv pip install -e .

      - name: Check constant-time properties
        run: |
          ct-analyzer --github src/crypto/*.c

GitLab CI

ct-check:
  stage: test
  script:
    - uv pip install -e .
    - ct-analyzer --json src/crypto/*.c > ct-report.json
  artifacts:
    reports:
      codequality: ct-report.json

Limitations

  1. Compiler Output Analysis: Analyzes what the compiler produces, not runtime behavior. Cannot detect:

    • Cache timing attacks from memory access patterns
    • Microarchitectural side-channels (Spectre, etc.)
    • Processor-specific optimizations
  2. No Data Flow Analysis: Flags all dangerous instructions regardless of whether they operate on secret data. Manual review is needed to determine if flagged code handles secrets. This means false positives are expected - for example, division used in loop bounds with public constants will be flagged even though it's not a vulnerability.

  3. False Positive Verification: For each flagged violation, verify the operands:

    • If operands are compile-time constants or public parameters → likely false positive
    • If operands are derived from keys, plaintext, or secrets → true positive
    • See the SKILL.md documentation for detailed triage guidance
  4. Compiler Variations: Different compilers/versions may produce different assembly. Test with:

    • Multiple optimization levels
    • Multiple compilers
    • Target production architectures
  5. Scripting Languages: PHP, JavaScript/TypeScript, Python, and Ruby are supported via bytecode analysis.

Running Tests

python3 ct_analyzer/tests/test_analyzer.py

References

Acknowledgments

Based on the test_ct utility created for ML-DSA.