touchmarkon Stellar

DocsStellar testnet

Touchmark documentation

Touchmark audits AI agents that hold Stellar authority: agents that keep keys, sign XDR, pay x402 and MPP invoices, authenticate with SEP-10 or drive Soroban contracts. It reads the agent's code in any language, answers a fixed ledger of Stellar-specific checks, has an independent model call re-check every serious finding at the line it cites, and writes the result to Stellar where anyone can check it.

Touchmark accepts four kinds of reference:

ReferenceExampleWhat is audited
GitHub repository or folderhttps://github.com/owner/repo/tree/<commit>/agentThe source at that commit. A branch is pinned to its current commit first.
Registry agent id64 hex charactersThe source the registry record binds, with its card and endpoint hashes.
ContractC…The deployed WASM, its spec and its build attestation.
AccountG…Signers, thresholds, SEP-29 and SEP-1 read from Horizon.

Without source only on-chain evidence is reviewed, and such an audit can never pass. Nothing is executed: code is read, never run.

Quickstart

In the browser

  1. Open usetouchmark.app and paste a GitHub link, ideally pinned to a commit.
  2. Tick Deep review by a model to add the model review and the verifier. Without it only the deterministic rules run and the source stays on the server.
  3. Read the decision, the findings with the lines they cite, and the check ledger. Fix, then Compare the fixed commit.
  4. A passing agent can be issued a credential on Stellar testnet.

Over HTTP

curl -s https://usetouchmark.app/api/v1/demo/audit \
  -H 'Content-Type: application/json' \
  -d '{"reference": "https://github.com/furkan3152/Touchmark_agent/tree/main/examples/demo/unsafe-treasury-agent"}' \
  | jq '{decision, score, counts, stamp: .assay.stamp_url}'

From a terminal

git clone https://github.com/furkan3152/Touchmark_agent && cd Touchmark_agent
python3 -m venv .venv && .venv/bin/pip install -r requirements.txt
.venv/bin/python scripts/audit_agent.py https://github.com/owner/repo --no-deep   # rules only, no key needed

How an audit works

The reference is resolved and pinned first; every step says what it proved. The deterministic rules then run on every audit. With consent, the model review follows, in this order:

  1. File map. Every selected source file is read, with line numbers, and recorded with what it does: where it signs, pays, holds secrets, verifies signatures or authenticates callers. Each claim quotes its line; a quote that is not in the file is dropped.
  2. Investigation. The agent's operating model (purpose, triggers, trust boundaries, assets at risk), and an assessment of every data-flow path the rules traced.
  3. Check ledger. A fixed catalog of 28 checks in four groups: value movement; signing, network and submission; access, secrets and entry points; model behaviour and intent. Which checks apply follows from the capabilities observed, never from the model's choice. holds and fails must quote the deciding line.
  4. Challenge. The 20 fund, signing, access and model checks answered holds are re-examined by a pass whose only job is to find one path where the control is absent. A flipped answer becomes a finding.
  5. Focused file review. Each file that signs, pays, holds keys or verifies signed input gets its own review, with the Stellar criteria and the lessons from real defects that match what the file does. The model states each function's security invariants and checks them one by one.
  6. Verifier. A separate model call re-checks each medium-or-higher finding (up to 60 per audit) at the cited line, without seeing why it was raised: confirmed, partial, refuted or unverifiable.

Data-flow rules cover Python and JavaScript/TypeScript; pattern rules cover Rust, Go and Solidity. Agents in other languages (Java, Kotlin, C#, Swift, PHP, Ruby, Dart…) are selected and mapped like any other, and the model review does the work the rules cannot. Comments in the audited code never count as controls. Tests, mocks and examples are never rated as shipped code.

Decision

One rule, shared by the demo, the Studio, the CI action and credentials:

DecisionWhen
needs_fixesAn actionable risk exists, or accumulated risks pushed the score below the pass mark of 65.
needs_evidenceNo actionable risk, but the core of the agent (prompt, tools and the code that holds authority) was not fully reviewed, or a medium payment bypass was never verified.
passNo actionable risk, with the full core surface reviewed by the model review and the verifier.

An actionable risk is a high or critical risk backed by source, configuration or live evidence, or a medium bypass of a payment or security control that the verifier confirmed at the quoted line. The rules alone never give a pass: a pass needs the full review.

Score

The score is an itemised list of risk points, one entry per finding, so every point can be traced:

cost   = severity weight × evidence × confidence × scope × verifier weight
weight = critical 30 · high 14 · medium 6 · low 2
evidence: confirmed 1 · inferred 0.6        scope: ×1 for one place, up to ×1.75
verifier: confirmed 1 · partial 0.8 · unchecked 0.75

score  = 100 × e^(−risk points / 100)
  • Repeats of one root cause add 30% (one missing spend control is not charged five times); a further finding of a category already charged counts half; further findings of one severity have diminishing returns.
  • Evidence gaps cost nothing: they limit the decision, not the score.
  • A confirmed critical risk holds the score at 39 or below; any other actionable risk at 64 or below, so a risky agent never looks passable.
  • Bands: 85 and above safe, 65 caution, 40 high risk, below 40 blocklist. The credential contract refuses scores under 65.

Every report carries its breakdown under result.score_breakdown, and the demo shows it under Why this score?

Severity

A model finding does not choose its own severity. It records facts, and the severity follows from them:

FactValues
What is at riskfunds, keys, account control, a service credential, the operator's own spend, a paid service not delivered, data…
Who can trigger itanyone, an authenticated user, the operator only, nobody yet (the code cannot run)
Networkmainnet, testnet, configurable
Does it shipshipped code, or tests, mocks and examples (never rated as shipped)

A claim about funds, keys or account control is lowered when the agent has no Stellar authority and the cited file does not authenticate callers, or when the file is front-end view code with no authority capability. A contract's own files are exempt. Rule findings carry the rule's severity, which the verifier may lower; a refuted critical becomes high, never disappears.

The report

Every audit produces one canonical JSON report and its SHA-256. The fields most clients need:

FieldMeaning
review_decisionpass, needs_fixes or needs_evidence
result.overall_score, result.score_breakdownThe score and every deduction behind it
findings[]id, severity, kind (risk, evidence_gap, warning), title, evidence, remediation, verification (verdict, file, line, reason)
deep_review.ledger.checks[]Each check: verdict (holds, fails, not_applicable, cannot_determine, unanswered), the quoted line, and challenged_from when the challenge flipped it
deep_review.coverageWhat was read, and every file not reviewed with the reason
repositoryCommit, selected files, excluded scope, the .tmark and the ownership claim

HTTP API

Base URL https://usetouchmark.app. JSON in and out; errors are {"detail": "…"} with a 4xx or 5xx status. No key is needed for the public endpoints below; they are rate limited (see Limits).

POST/api/v1/demo/audit

Audit a reference. The model review runs only with consent and within the allowance; otherwise the rules run and the answer says why (ai.skipped).

referencestring, requiredGitHub URL, agent id, C… or G…
model_review_consentboolean, default falseSend the source to the model review and the verifier
tmarkstring, optionalA .tmark to use as if committed. It is validated first; a 422 lists each error with its field and line

The answer: decision, score, report_sha256, report_json, source (repository, revision, path), risks[] and gaps[] (summaries), counts, breakdown, scope, tmark, ai (what the review and the verifier did), issuance (whether a credential can be issued) and assay (the assay book entry and stamp).

POST/api/v1/demo/audit/continue · /api/v1/demo/audit/cancel

{"job_id": "…"}. Run the next leg of a long audit, or stop it before its next model call. See Streaming and jobs.

POST/api/v1/demo/compare

{"before": "…", "after": "…"}. Audit two versions with the deterministic rules and compare: what the fix closed, what is still open, what is new. Both sides are rules only, and the answer says so (mode: rules_only).

GET/api/v1/demo/config

What this deployment offers: network, contracts, whether credentials can be issued, the model review (model_review.available, model, verifier, limits) and the allowance left to this client.

POST/api/v1/tmark/check · GET/api/v1/tmark/template · POST/api/v1/tmark/template

Validate a .tmark ({"text": "…"} → valid, errors, error_details with field and line, normalized, summary); get a template; or draft one from a repository ({"reference": "…"}) or from source files ({"files": {path: text}}). Nothing is stored.

POST/api/v1/demo/credential/prepare · /api/v1/demo/credential

Issue a credential for a passing audit with the issuance.ticket from its result. When the repository names an owner, prepare returns the registration for the owner to sign in their wallet, and the signed XDR goes with the ticket. Issuing takes four Soroban transactions, about a minute: send Accept: application/x-ndjson to receive heartbeats until the result line. Issuing again for the same report reads the credential back from the ledger.

GET/api/v1/assay/book · /api/v1/assay/entry/{entry}

The assay book's statistics and latest entries; one entry as stamped. See Verify an audit.

POST/api/v1/assay/claim · /api/v1/assay/pay/prepare · /api/v1/assay/pay/submit · GET/api/v1/assay/report/{entry}.json

Release a report for its testnet payment: look for the payment on Horizon ({"entry", "tx_hash"?}), prepare an unsigned payment for a wallet ({"entry", "payer"}), relay the signed one ({"entry", "signed_xdr"}), and download the report once paid (402 before).

GET/api/v1/health

Version, network and which modes are on (modes.deep_review).

Streaming and jobs

A model review can take longer than one request. On a deployment that keeps jobs, an audit is a job that runs over one or more requests ("legs"); each leg saves its place. Ask for a stream to see it run:

curl -sN https://usetouchmark.app/api/v1/demo/audit \
  -H 'Accept: application/x-ndjson' -H 'Content-Type: application/json' \
  -d '{"reference": "https://github.com/owner/repo/tree/<commit>", "model_review_consent": true}'

Each line is one JSON event:

jobThe job id and where it stands. Keep the job_id.
stageresolving, fetching, rules, deep_review (files read), verifier (findings checked), done
heartbeatEvery few seconds, so proxies keep the connection
resultThe finished audit (same object as the plain answer). The stream ends.
continueThe leg ended; POST /continue with the job_id to run the next one
busyAnother request is advancing this job; try again after retry_after seconds
cancelled · errorThe job stopped; error carries status and detail

Without the stream header the answer is the result itself, or a 202 with the continue event while the job is unfinished. /continue is idempotent: a finished job returns its result again, and two continues of one job never both run. Jobs expire after 24 hours.

A client that follows a job to the end: research/benchmark/real/run_live.py.

Command line

# rules only: no key, nothing leaves your machine
.venv/bin/python scripts/audit_agent.py https://github.com/owner/repo --no-deep --out report.json

# full review, as on the demo
ENABLE_DEEP_REVIEW=1 DEEP_REVIEW_MODEL=claude-haiku-5-5 VERIFIER_MODEL=claude-haiku-5-5 ANTHROPIC_API_KEY=… \
  .venv/bin/python scripts/audit_agent.py https://github.com/owner/repo/tree/<commit>

# audit the fix and compare
.venv/bin/python scripts/audit_agent.py <vulnerable-ref> --compare <fixed-ref>
--networktestnet (default) or mainnet
--purposeWhat the agent is for, when the repository does not say
--resolve-onlyResolve and pin the reference, print every proof step, audit nothing
--no-deepDeterministic rules only
--outWrite the report JSON
--compareA second reference to audit and compare

Run locally, the model review stops after DEEP_REVIEW_TIMEOUT_SECONDS (240 s by default, at most 900); raise it for large repositories.

GitHub Action

The composite action runs the deterministic analysis on every push, validates the .tmark and annotates the cited lines. Pin both actions to a commit:

- uses: actions/checkout@<commit-sha>
- uses: furkan3152/Touchmark_agent@<commit-sha>
  with:
    path: .              # folder to audit
    fail-on: needs_fixes # fail the job on an actionable risk; never only reports
    annotate: medium     # lowest severity annotated on the code

Outputs: decision, score, tmark (valid, invalid or missing) and report (path of the report JSON); a summary is written to the job page. The rules alone never give a pass, so CI never shows a result that could earn a credential.

.tmark: declare your agent

A .tmark at the repository root states what the agent may do: network, keys and what it signs, spending limits, recipients, approval and tools. Touchmark holds the code to every claim; a declaration never closes a risk on its own. A declaration that says more than the code enforces becomes a finding: authority mismatch, limit not enforced, network mismatch.

{
  "$schema": "https://usetouchmark.app/schemas/tmark-v1.json",
  "tmark": "1",
  "agent": { "name": "Treasury agent", "kind": "mcp_server", "network": "testnet",
             "purpose": "Pays two listed suppliers in XLM on request." },
  "owner": { "stellar_account": "G…" },
  "authority": { "holds_keys": true, "key_source": "env", "signs": ["payments"] },
  "limits": { "per_payment": { "amount": "500", "asset": "XLM" },
              "recipients": "allowlist", "human_approval": "never" }
}
python scripts/tmark.py init path/to/repo    # a valid draft from what the code can do
python scripts/tmark.py check path/to/repo   # lists every error

Build one in the browser with the .tmark builder. Every field: docs/TMARK.md. Schema: /schemas/tmark-v1.json.

The assay book

Every audit the public demo runs is entered in the assay book and stamped on Stellar testnet. The entry is a small canonical JSON record: the agent (repository, commit, folder), the decision, the score, the finding counts, whether the model review completed, and the SHA-256 of the full report. Its SHA-256 is written as the MEMO_HASH of a transaction from the book account, a bump-sequence operation that changes nothing else.

sha256(canonical entry) == memo of the stamp transaction entry.report_sha256 == sha256(report JSON)

The full report is released for 2 XLM on testnet. The payer sends it with the text memo tm:<first 20 hex of the entry>, from Freighter or any wallet; the server reads the payment from Horizon (never from the browser), releases the report, and sends the payer a receipt stamp: a one-stroop payment whose memo is the entry hash. The payer's own account history shows both sides.

Records are written once and never changed. The book page counts every entry and links each row to its transactions.

Verify an audit

Nothing has to be taken on trust. With an entry id from the book or from an audit's assay.entry_sha256:

E=<entry>
curl -s https://usetouchmark.app/api/v1/assay/entry/$E > entry.json

# 1. the entry hashes to its id
python3 -c "import json,hashlib;e=json.load(open('entry.json'))['entry'];print(hashlib.sha256(json.dumps(e,sort_keys=True,separators=(',',':'),ensure_ascii=False).encode()).hexdigest())"

# 2. the stamp transaction carries that hash as its memo, from the book account
TX=$(jq -r .stamp_tx entry.json)
curl -s https://horizon-testnet.stellar.org/transactions/$TX | jq -r '.source_account, .created_at'
curl -s https://horizon-testnet.stellar.org/transactions/$TX \
  | python3 -c "import sys,json,base64;print(base64.b64decode(json.load(sys.stdin)['memo']).hex())"   # equals the entry id

# 3. a paid report hashes to the entry's report_sha256
curl -s https://usetouchmark.app/api/v1/assay/report/$E.json | sha256sum

The canonical form is JSON with sorted keys, no spaces and UTF-8 text. Each step uses only public data: the entry, a public Horizon server and the report bytes.

All of it, for every entry, in one script that needs only Python: scripts/verify_assay.py.

python3 scripts/verify_assay.py --ledger --latest 10   # entries, and the book's totals counted from the ledger
python3 scripts/verify_assay.py --latest 10 --json --archive > proof.json
pip install stellar-sdk && python3 scripts/verify_assay.py --verify-archive proof.json   # offline, after a testnet reset

--ledger lists every stamp, payment and receipt of the book account from Horizon, checks that the site publishes each stamped entry, and that the totals on the book match the ledger: an entry the site hid, or a figure it inflated, fails. What the ledger cannot prove: that a report is right (each finding cites a line in a public commit, so anyone can read it), or that two payers are two people.

Credentials

A passing audit with the full review can be recorded as a non-transferable credential. Soroban contracts, not the server, decide whether a credential exists and is still valid:

  1. The agent is registered. When the repository's .tmark names owner.stellar_account, that account signs the registration in its own wallet; otherwise the credential names the demo operator and says ownership was not verified.
  2. The approved validator writes the report hash and score to the registry.
  3. The credential contract issues one token bound to that report and that agent version. It refuses scores below 65; it expires after at most 90 days; it can be revoked; and is_current re-checks expiry, revocation, the validator and the agent's current version on every read. A changed agent is no longer current.

A credential vouches for what the repository says about itself, so an audit that used a .tmark supplied with the request cannot be credentialed: commit the declaration and audit again.

Contracts

ContractEnforces
AgentRegistryOwner-authorized, versioned agents; an allowlist of validators; a validator pinned per request that responds once with a score, a report URI and a non-zero report hash, only while the agent is unchanged.
AuditCredentialOne non-transferable credential per completed validation; refuses scores under 65, a validator no longer approved and a changed agent; expiry at most 90 days; revocation is permanent.
AuditEscrowSettles in a Stellar Asset Contract and pays only for the assigned validator's matching report and score; capped fees, deadlines, refunds and disputes.
AgentAccountA custom account (__check_auth) for an agent's session keys: allowed recipients and assets, a per-call maximum, a cumulative budget, nonces, expiry, rotation and revocation.

No contract has an upgrade function. The demo's registry and credential contracts on testnet:

AgentRegistryCBIZOECNI22Y6RXANQWS7SYL2QGV2ZX2QZ4CS3GOOETFJ66SIN6YJZ6W
AuditCredentialCBBTOBL6XTLWYPT5GSVUA3JWNQMVMT4DGGHEGZ4NHC3V4SLXKP5AO4R5
Operator, validator and book accountGCRGIGXMFQ632Q65TUDVPHWUIPEIDVKR4TJYIEFE6DFHMMEPGDY3VOSU

Every deployment and its transactions: deployments/. The contracts have a self-assessed review and no external audit yet.

Run it yourself

python3 -m venv .venv && .venv/bin/pip install -r requirements.txt
.venv/bin/uvicorn app:app --port 8000          # the demo at http://localhost:8000
.venv/bin/python -m pytest -q && cargo test --workspace
VariablePurpose
ENABLE_DEEP_REVIEW, ANTHROPIC_API_KEYTurn on the model review and the verifier (they still run only with the visitor's consent)
DEEP_REVIEW_MODEL, VERIFIER_MODEL, …_EFFORTModels and effort of the review and the verifier
DEEP_REVIEW_MAX_COST_USD_PER_AUDIT, DEEP_REVIEW_DAILY_COST_USDSpend caps per audit and per UTC day. A reached cap ends the review cleanly and lists every unread file
DEEP_REVIEW_PRICE_*_PER_MTOKPrices that turn measured usage into cost for the caps
DEEP_REVIEW_CLIENT_HOURLY_LIMIT, …_HOURLY_LIMIT, …_DAILY_LIMITModel reviews per client per hour, overall per hour and per day
BLOB_READ_WRITE_TOKENPrivate Vercel Blob store for jobs, allowances and the assay book. Without a durable store on serverless the model review is off
DEMO_OPERATOR_SECRETTestnet key that administers the demo registry, acts as validator and stamps the assay book
ENABLE_ASSAY_BOOK, ASSAY_PRICE_XLMStamp every audit on testnet; the testnet price of a report (2)
ENABLE_ONCHAIN_VERIFICATIONRead the agent account's signers and thresholds, SEP-29 and SEP-1 from Horizon
STELLAR_NETWORK, ALLOW_MAINNETNetwork selection; mainnet stays off unless explicitly allowed

All settings: docs/ARCHITECTURE.md.

Limits

  • A pass is not a guarantee. It means no actionable risk was found in the reviewed code, not that the agent is safe.
  • Nothing is executed. The audit reads code; a testnet execution probe is prototyped in research/testnet-probe.
  • A single run is not deterministic. Two runs of the model review can differ; the benchmark publishes both runs. The verifier re-checks at most 60 findings per audit.
  • Public demo. Starting an audit or a comparison is limited per address per hour, the model review per client per hour, and model spend per day; past them audits run the rules only and say so.
  • Testnet by default. Testnet XLM has no monetary value.

Measured on real Stellar defects that maintainers fixed upstream: the benchmark.

What leaves your hands

  • Without consent, the source stays on the server and only the deterministic rules run.
  • With consent, the selected source files are sent to the model provider for the review and to a second call for the verifier.
  • The assay book stores the report of every demo audit privately, and publishes only the entry (agent, commit, decision, score and counts). The full report is released to whoever pays for it on testnet; audit public repositories only on the demo.
  • No response or report carries measured tokens or cost. Client addresses are hashed for the allowance and never stored raw.

Found a vulnerability? See SECURITY.md.