Skip to content
All posts

· 7 min read

Claude Code for Security Tooling: Build Tools on Odin v1

Run Claude Code on Odin models with the claudeodin command to build log parsers, a recon normalizer and a YARA test harness, with spend limits and a sandbox.

Most security teams carry a pile of small scripts: a parser for one odd log format, a converter between two scanner outputs, a harness that runs rules against test files. Claude Code is good at writing exactly this kind of tool, and with InterOdin it can run on Odin v1, which does not refuse legitimate security work. This post covers the setup, how to keep it safe, and three tools worth building: a log parser, a recon-data normalizer for an authorized engagement, and a YARA test harness.

Why a separate claudeodin command

The InterOdin setup script does not change your normal Claude Code. It adds a second command, claudeodin, that runs the same Claude Code on Odin models with its own settings folder, ~/.claude-odin.

That separation matters in practice:

  • Your normal claude command keeps your Claude account, settings, sessions and history. Nothing is overwritten.
  • claudeodin has its own history, so security sessions with sample data and engagement notes stay out of your everyday sessions.
  • You can switch per task. Use claude for general work and claudeodin when the job is malware triage, detection content or tooling for a test.

Setup

You need Node.js 18 or newer, Claude Code already installed, and an InterOdin API key (it starts with io-sk-) from the console.

bash
curl -fsSL https://docs.interodin.com/claude-code-setup.mjs -o claude-code-setup.mjsnode claude-code-setup.mjsclaudeodin

The script asks for your key with the input hidden and writes the settings into ~/.claude-odin. It is short and has no dependencies, so read it before you run it. Inside a session, /status shows which base URL is active.

To undo everything later:

bash
node claude-code-setup.mjs delete

This removes the launcher and the settings the script added. The full guide, including a manual setup, is at Claude Code on InterOdin.

Choosing a model with /model

Inside claudeodin, the /model menu lists Odin v1 and Odin Flash.

  • Odin v1 (odin-v1): the most capable model, with a 1,000,000-token context window and thinking on by default. Use it for designing a tool, reading a large codebase or debugging a parser against a messy real-world format.
  • Odin Flash (odin-flash): faster and cheaper, with a 262,144-token context. Good for small edits, renames, writing tests and repetitive changes.

A common rhythm: plan and build the first version on Odin v1, then switch to Odin Flash for the cleanup work.

One limit to know: the models are text only. If you paste a screenshot of a log viewer, it fails. Paste the log lines instead.

Give the key a monthly spend limit

An agent session is not one request. Each tool call, file read and retry is its own API call, and a long session adds up. Create a separate API key just for Claude Code and set a monthly spend limit on it in the console. If a session loops on a failing test overnight, the limit caps the damage. Billing is prepaid credits in EUR or USD with no subscription; see pricing for current rates.

Run it in a container or VM when it executes commands

Claude Code can run shell commands and edit files. By default it asks before each one. Many people turn the prompts off once they trust a workflow, and that is when isolation matters.

Odin v1 answers directly, so there is less of a brake on the model side. Put the brake in the environment instead:

  • Run claudeodin in a disposable container or VM with only the project folder mounted.
  • Keep credentials, SSH keys and client data that the tool does not need out of that environment.
  • Never let it execute malware samples. Any sample work belongs in a dedicated, isolated analysis VM, and the harness should only read files, never run them.
  • For recon tooling, make sure the environment cannot reach real targets unless they are in your written scope.

A minimal container for a tooling project looks like this:

bash
docker run --rm -it \  -v "$PWD":/work -w /work \  -e INTERODIN_API_KEY \  python:3.12 bash

Install Node.js and Claude Code inside, run the setup script there, and the session can only touch /work.

Tool 1: a log parser

Start with something low risk. Suppose your proxy writes a custom format that your SIEM does not parse. Give Claude Code a sample of real lines (redacted) and a clear target:

text
Write parse_proxy.py. Input: lines like the ones in samples/proxy.log.Output: one JSON object per line with timestamp (ISO 8601 UTC), src_ip,user, method, url, status, bytes, user_agent. Lines that do not match goto stderr with the line number. Add pytest tests using the sample lines,including the two malformed ones at the end.

What to expect: a parser built around one compiled regular expression or a small tokenizer, timestamp conversion with the timezone handled explicitly, tests that cover the normal and malformed lines, and a note on any field it had to guess, for example whether a - in the user column means an empty value. Run the tests yourself before you trust the output.

Tool 2: a recon-data normalizer for an authorized engagement

On a test, data arrives from several tools in several formats. A normalizer merges them into one inventory. The key design rule is that scope is enforced in code, not by memory.

Put the scope from your rules of engagement in a file, for example:

text
# scope.txt, from the signed rules of engagement192.0.2.0/24198.51.100.0/25

Then ask:

text
Build normalize.py for an authorized external test. Inputs: nmap XMLfiles in raw/nmap/ and a CSV export from our web scanner in raw/web/.Output: inventory.json with one entry per host: ip, hostnames, open portswith service and version, and web findings. Load scope.txt and drop anyhost outside it, logging each dropped host to out_of_scope.log. The toolonly reads files; it must not send any network traffic.

Expect a parser for each input, a merge step keyed on IP address, scope filtering with Python's ipaddress module, and tests that include a host outside the range to prove it gets dropped. That out-of-scope log is useful evidence later: it shows that data you were not authorized to test never entered the report.

Once the inventory exists, the tool can call the API directly to draft engagement notes. Odin Flash is a good fit for this kind of summary:

python
from openai import OpenAIimport jsonimport os
client = OpenAI(    base_url="https://api.interodin.com/v1",    api_key=os.environ["INTERODIN_API_KEY"],)
inventory = json.load(open("inventory.json", encoding="utf-8"))
resp = client.chat.completions.create(    model="odin-flash",    messages=[        {            "role": "system",            "content": "You help a penetration tester on an authorized engagement. "                       "Summarize the attack surface by host. Do not suggest "                       "testing anything outside the provided inventory.",        },        {"role": "user", "content": json.dumps(inventory, indent=2)},    ],)print(resp.choices[0].message.content)

API prompts and completions are never stored, only billing metadata. Still, redact client names you do not need. For turning these notes into a deliverable, see Pentest report writing with AI.

Tool 3: a YARA test harness

Rules are only as good as their testing. A harness runs a rule set against two folders, known samples and known-benign files, and reports misses and false positives.

text
Write yara_harness.py using yara-python. Compile every .yar file inrules/. Scan positives/ (each file should match) and benign/ (nothingshould match). Never execute any file, only read it. Print a table ofrule, true positives, misses and false positives, and exit non-zero ifany rule has a false positive so CI can catch it.

The core of what you get back looks something like this:

python
import pathlibimport yara
rules = yara.compile(filepaths={p.stem: str(p) for p in pathlib.Path("rules").glob("*.yar")})
def scan(folder):    results = {}    for path in pathlib.Path(folder).rglob("*"):        if path.is_file():            results[str(path)] = [m.rule for m in rules.match(str(path))]    return results

Keep the positives folder inside your isolated analysis VM, and fill the benign folder with a broad set of clean files: installers, scripts and documents from your own environment. To write the rules themselves, see Sigma and YARA rules with AI. For the triage that produces the strings in the first place, see Malware analysis with Odin.

Review what it builds

A tool written by an agent is still your tool, and in security work a quiet bug can mean a missed detection or a host outside scope in your report. Before you rely on any of these tools:

  • Read the diff. Look for hardcoded paths, swallowed exceptions and anything that touches the network when it should not.
  • Run the tests yourself, and add one case the agent did not think of, such as an empty file or a line in an unexpected encoding.
  • Feed it known input with a known answer. A parser should round-trip your sample lines, the normalizer should drop your test host outside scope, and the harness should flag a deliberate false positive.
  • Keep the prompts you used in the repository next to the code, so the next person can see what the tool was asked to do.

Get started

Create an API key in the console with a monthly spend limit, run the setup script, and follow the Claude Code guide. For direct API use, start with the quickstart. Keep every tool and target within your authorized scope and the Acceptable Use Policy.