QVIS·CVSTODIET·IPSOS·CVSTODES

Who watches the watchmen? — Custos does.

Watch the code that watches your money.

Custos is an LLM security auditor for Bitcoin, Lightning, Arkade & L2 codebases. Point it at a directory — or just a repo name — and it reads the security-critical source with a Bitcoin-specific threat model, writing up the bugs that lose funds inside an end-to-end-encrypted enclave. Cleartext never leaves your machine.

$ npx @tiero/custos run --repo arkade-os/wallet --auto
Read a real audit ↗

MIT licensed  ·  audits any git URL, no checkout  ·  report.md + SARIF 2.1.0  ·  GitHub Action for release tags

custos — audit · zsh
$ custos run --repo arkade-os/wallet#v0.4.1 --auto

custos 0.1.0 — the watchman is awake
cloning   github.com/arkade-os/wallet.git#v0.4.1 (shallow)
enclave   prem · glm-5.2 · end-to-end encrypted

discover  142 files scanned → 6 crown jewels ranked
bundle    wallet-seed · signer-rpc · exit-paths
audit     3 bundles sealed → enclave (cleartext never leaves)
verify    9 proposed → 4 confirmed · 5 refuted

CRITICAL  seed encrypted with hardcoded default password   confirmed
CRITICAL  LoadSigner RPC reachable without a macaroon      confirmed
HIGH      PBKDF2 at 10,000 iterations for seed-at-rest     confirmed
MEDIUM    non-hardened BIP32 path in nostr derivation      confirmed

 wrote audit/report.md + audit/findings.sarif  (exit 1 · --fail-on high)

Seven ways funds die

Bitcoin-tuned bug classes,
not generic CWEs.

General SAST cries wolf at zero-aux BIP340 and MuSig2's commitment rounds. Custos knows the non-bugs — and hunts the classes that actually drain wallets.

Entropy & RNG

Including the Coldcard class: a CSPRNG that runs but doesn't. Seeded from time, PIDs, or nothing at all.

Key management

Derivable keys, non-hardened paths, weak KDFs, hardcoded default passwords guarding real seeds.

Nonces · MuSig2 · Schnorr

Nonce reuse, missing commitment rounds, rogue-key aggregation — the mistakes that hand over the private key.

Signature verification

Wrong sighash, attacker-chosen sighash types, unverified taproot tweaks, replayable proofs.

Consensus & fund logic

Amount mismatches, double spends, broken forfeit and exit paths, timelocks that don't hold.

Auth

Macaroon bypass, wrongly-whitelisted RPCs — admin surface reachable with no credential at all.

DoS

Unbounded recursion, parse bombs, hash-table growth — attacker-controlled input doing unbounded work.

The method

Discover, bundle, audit, verify.

One command runs the whole rite — or curate the manifest yourself for deeper audits. Findings are model-proposed, then verified; reports separate confirmed bugs from refuted false positives.

I

Discover

custos discover ranks the repo's security-critical files and seeds a manifest — the crown jewels, not the whole codebase. Point it at a path, a git URL, or owner/repo#v1.4.2 to pin the exact tag you shipped; remotes get shallow-cloned for you.

II

Bundle

Group files that share a trust boundary, so the model reasons across caller and callee instead of one file at a time.

III

Audit

Each bundle is encrypted in-process and read inside a confidential enclave against the Bitcoin threat model. Your source never travels in cleartext.

IV

Verify & report

Proposed findings are checked; plausible-but-wrong crypto claims get refuted and documented. Out comes report.md plus SARIF 2.1.0 for code scanning.

Confidential by construction

Your code is the secret.
Custos treats it like one.

Auditing proprietary wallet code with a cloud LLM usually means mailing your crown jewels to someone else's datacenter. Custos encrypts every request in-process to a Prem confidential enclave — the client runs inside your machine or CI runner.

Your trust boundary
../my-walletsource
custosbundles + threat model
encrypt in-processe2e
Prem enclave
glm-5.2default model
decrypt · audit · sealin-enclave
findings ⇢ backencrypted
⇠ returns audit/report.md ⇠ returns audit/findings.sarif cleartext source: never leaves

In CI the enclave client runs inside your runner — the same boundary that already holds your source. No third party ever sees plaintext. Override PREM_ENCLAVE_URL to point at your own endpoints.

Not hypothetical

It already caught fund-losing bugs.

A real run against the arkade-os stack — arkd, web wallet, and four SDKs — proposed 28 findings. Verification confirmed ten, two of them critical.

CRITICALconfirmed

Unauthenticated RPC hijacks the server signer

LoadSigner sat on the auth whitelist — no macaroon required. Anyone reaching the port could install their own signer key or URL and control the server side of every MuSig2 signing round: co-sign malicious trees, steal from users, or halt the service.

arkd/…/handlers/signer_manager.go
arkd/…/permissions/permissions.go · auth.go

CRITICALconfirmed

Wallet seed encrypted with the password 'noah'

The web wallet shipped a hardcoded default password constant and used it to encrypt the seed when users skipped setting one — a passwordless-by-default wallet, with the key sitting in localStorage for any XSS to exfiltrate.

wallet/src/lib/constants.ts · privateKey.ts
wallet/src/screens/Init/Init.tsx

28 proposed 10 confirmed 2 critical plausible-but-wrong crypto claims refuted & documented Full assessment ↗

The GitHub Action

Post the watch
where it matters.

Custos ships as a composite action — checkout, audit, SARIF, done. But a run is one enclave request per bundle, paced and metered, so the trigger you pick is the cost model. Audit what you ship, not every keystroke.

.github/workflows/custos-release.yml
on:
  push:
    tags: ["v*"]

permissions:
  contents: read
  security-events: write

jobs:
  audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: tiero/custos/.github/actions/custos@v0
        with:
          repo-path: .
          fail-on: critical
          prem-api-key: ${{ secrets.PREM_API_KEY }}

The audit reads the exact commit you are about to ship, at the one moment the code stops being yours and starts holding other people's money.

Runs
Once per release — the cheapest useful cadence
Scope
Auto-discovered crown jewels, no manifest needed
Gate
Don't cut the release on a red check — fail-on: critical

Fire on the tag push, not on release: published. A published release can't be un-published by a failing check — by then the audit is a record, not a gate.

.github/workflows/custos-drift.yml
on:
  schedule:
    - cron: "0 3 * * 1"   # Mondays, 03:00 UTC
  workflow_dispatch:

permissions:
  contents: read
  security-events: write

jobs:
  audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: tiero/custos/.github/actions/custos@v0
        with:
          prem-api-key: ${{ secrets.PREM_API_KEY }}
          # no fail-on: file alerts, never block

Code that hasn't changed still gets re-read as the threat model and the model itself improve. Last quarter's clean bill is not this quarter's.

Runs
Four or five a month — predictable, budgetable
Scope
Whole repo, auto-discovered
Gate
None — it files alerts, it never blocks anyone

Keep workflow_dispatch on it. When a new class of bug lands in docs/bug-patterns.md, you'll want to re-run the watch by hand that afternoon.

.github/workflows/custos-pr.yml
on:
  pull_request:
    paths:               # only the trust boundaries
      - "src/crypto/**"
      - "src/wallet/keys/**"
      - "src/consensus/**"

permissions:
  contents: read
  security-events: write

jobs:
  audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: tiero/custos/.github/actions/custos@v0
        with:
          target-module: targets/crown-jewels.json
          fail-on: high
          prem-api-key: ${{ secrets.PREM_API_KEY }}

A paths: filter plus a curated manifest keeps the read scoped to the one trust boundary the PR actually touched — the only shape cheap enough to run while code is still in review.

Runs
Only on PRs that touch key, signing, or consensus code
Scope
A hand-curated manifest, not the whole repo
Gate
Blocks the merge, not the branch — fail-on: high

Skip this one on a busy repo. Without the paths: filter and a small manifest it degrades into auditing every push, which is the thing to avoid.

Don't wire it to every push. Each run is a fresh set of enclave requests — paced 12s apart by default, with an 8000-token budget per bundle — so a push-triggered audit mostly pays to re-read code that didn't change. Custos is a ship gate and a standing watch, not a linter.

SARIF into code scanning

Findings land as native GitHub security alerts, file- and line-anchored.

Report as an artifact

The full report.md — confirmed and refuted — attached to every run.

Source never leaves

The enclave client runs inside your runner: the boundary that already holds the code.

Where it stands

Depth over breadth.

DeepSecShannonCustos
Domaingeneral app-sec / CWEblack-box API pentestBitcoin crypto & fund safety
Needssourcea live targetsource only
InferencecloudcloudE2E-encrypted enclave

Same static+LLM shape DeepSec pioneered — the edge is domain depth and confidentiality. Run Shannon black-box and Custos white-box together for full coverage.

An honest watchman.