The journey of creating Cipherscope

Cipherscope started as a simple question: “What crypto is actually used in a large codebase?” I wanted a tool that could answer it without hand-waving, and without turning into a slow, noisy scanner. The result is a command-line tool that detects crypto libraries and algorithms with a precision bias. The code lives at github.com/script3r/cipherscope.

One concrete use-case is building a cryptographic inventory for post-quantum readiness. You can’t plan migrations without knowing where legacy primitives live, which libraries expose them, and how widely they’re embedded. Cipherscope gives you that map: a fast, defensible picture of what needs review before PQC work begins.

The shape of the problem

Grep was too blunt. Pure AST analysis was too heavy. The compromise became a two-phase approach: discover relevant files fast, then parse only what matters. That “lightweight then precise” rhythm still defines the tool’s feel.

Agentic, vibe-driven iteration

I wrote Cipherscope in a loop that felt more like conversation than construction: sketch a tiny feature, run it, listen to the output, adjust the edges. The “vibe” here is restraint—if a feature doesn’t sharpen signal, it doesn’t stay.

The agentic part was letting the tool tell me what it wanted to be. Every scan produced a new constraint: performance, false positives, missing libraries. I kept the loop tight, one constraint at a time, until the tool read like a clean sentence.

Patterns as a contract

The most durable design choice was making detection patterns a separate file. A simple TOML schema turned into a contract between the scanner and the world it reads.

[[library]]
name = "OpenSSL"
languages = ["C", "C++"]
[library.patterns]
include = [
  "^\\s*#\\s*include\\s*<openssl/[A-Za-z0-9_./-]+>",
]
apis = [
  "\\bEVP_[A-Za-z0-9_]+\\s*\\(",
  "\\bHMAC\\s*\\(",
  "\\bRSA_[A-Za-z0-9_]+\\s*\\(",
  "\\bDSA_[A-Za-z0-9_]+\\s*\\(",
  "\\bEC_KEY_[A-Za-z0-9_]+\\s*\\(",
  "\\bECDSA_[A-Za-z0-9_]+\\s*\\(",
  "\\bED25519_[A-Za-z0-9_]+\\s*\\(",
  "\\bX509_[A-Za-z0-9_]+\\s*\\(",
  "\\bPKCS\\d_[A-Za-z0-9_]+\\s*\\(",
]

# Algorithm definitions for OpenSSL
[[library.algorithms]]
name = "RSA"
primitive = "signature"
nist_quantum_security_level = 0
symbol_patterns = [
  "\\bRSA_",
  "\\bEVP_PKEY_RSA",
]
[[library.algorithms.parameter_patterns]]
name = "keySize"
pattern = "RSA_generate_key_ex\\s*\\([^,]+,\\s*(\\d{4})"
default_value = 2048

When the patterns are explicit, the scanner can be small, and users can extend it without forking the code.

Outputs that can flow

Cipherscope streams JSONL so it can feed CI pipelines or security dashboards without ceremony. Each finding is a line: small, direct, machine-friendly.

{"assetType":"library","identifier":"OpenSSL","path":"src/crypto.c","evidence":{"line":2,"column":1}}
{"assetType":"algorithm","identifier":"AES-256-GCM","path":"src/crypto.c","evidence":{"line":42,"column":18},"metadata":{"keysize":256,"primitive":"symmetric"}}

What I kept, what I cut

I kept parallel discovery, Tree-sitter parsing, and an opinionated bias toward correctness. I cut everything else: UIs, dashboards, anything that distracted from scanning fast and reading clear results.

Building Cipherscope taught me that “agentic” doesn’t have to mean sprawling. It can mean a sharp loop, a quiet compass, and a tool that knows when to stay small.