dvalincode

MCP serverSecurity

dvalincode adds security scanning to your AI, so it can check code for security issues and confirm when a fix actually works. Scans are deterministic, meaning you get the same result for the same code every time. It runs without a model or API key, and it produces proof that a fix worked which can be verified offline.

Unavailable. This server has no hosted endpoint yet, so Ahel can't serve it.

Add to setup to save this item as a reference. Ahel cannot run it, and signing in will not install it.

What your AI can do with it

  • Scan code for security issues with consistent, repeatable results
  • Run security checks without needing a model or API key
  • Produce proof that a fix worked, verifiable offline

Getting started

  1. Save this item in Your setup as a reference.
  2. Read the source or reference documentation for its setup requirements. Saving it here does not connect it to your AI.
  3. Check this page for availability before trying to install it through Ahel.

From the project's README

As published by arthurpanhku/dvalincode in README.md.

English · 中文 · 🌐 dvalincode.dev

When an agent fixes a security finding, someone has to decide whether the fix worked. Almost every tool asks the model that wrote it — which is the one question a model cannot answer against its own interest.

Dvalin decides instead, and hands you the proof. It re-scans, runs your project's own tests itself, and reads the exit codes from processes it started. Who wrote the repair — our agent, Claude Code, Codex, Copilot, a person — is recorded and never consulted. The result is a Verified Fix Record: a small JSON file anyone can re-check offline, on a laptop with no network and no Dvalin state.

dvalin verify-fix fix-record.json
Fix record 2c9d71ac03e0 · VERIFIED · scan-and-checks
  executor: claude-code (recorded, not consulted)
  targets: 1 before · 0 remaining
  coverage: complete → complete
  introduced: 0 (gate high/new)
  outcome: verified
  ✓ test: npm run test (exit 0)
  audit: run verify-36509f42 @ 414644c75af0

That record says something narrow on purpose: these findings were gone, and these checks were observed to pass. It is not a claim that your code is safe, and Dvalin will not let it be read as one — every record carries what the scan actually covered, and a repair no check could confirm does not pass. The open profile →

A repair is a change, and a change can add as well as remove. So the record also carries what the re-scan saw that the first scan did not, and the gate threshold the verdict was reached under: a fix that removes an eval and introduces an SQL injection is recorded as regressed and does not verify. Neither does a record whose issuer never looked — introduced: not determined fails, because a verifier that skips the question must not score better than one that asks it and finds something.

Dvalin is the independent security runtime between code generation and merge. Humans, coding agents, and CI call the same versioned contract for discovery, remediation, and verification. It runs independently, or interoperates with specialist systems such as Codex Security through portable SARIF. Its built-in coding capability is a remediation executor — not the trust boundary, and not an attempt to compete with every general-purpose coding agent. See the security-agent strategy.


⏱️ 30 seconds, no install, no API key

npx dvalincode security scan .
# After installing the package: dvalin scan .

That is the whole thing. It runs the built-in rules for injection, hardcoded secrets, XSS, eval, and unsafe shell use against the current directory and prints what it found. No account, no model, no config, no code leaves your machine. The default policy runs only Dvalin Built-in, so the first scan always works. Add optional engines explicitly, or inspect their fixed install commands:

dvalin scanners list
dvalin scanners install semgrep       # review the command
dvalin scanners install semgrep --yes # execute it under Dvalin policy

Measure with the scanner your CI gate uses

If Snyk is what blocks your merges, verify against Snyk. A fix judged "clean" by a different engine is a fix the gate can still reject, and that mismatch is what turns one agent repair into several rounds of push, fail, and rebase.

npm install -g snyk && snyk auth      # or set SNYK_TOKEN
dvalin scan . --scanners builtin,snyk-code,snyk-oss

snyk-code (SAST) and snyk-oss (dependencies) are engines like any other: their findings carry the same fingerprints, coverage and gate, and a failed login is reported as an engine error, which makes the scan partial, never clean. They run only when named, because Snyk Code uploads source to Snyk. Name them in dvalin.security.json to make them part of the policy, and reverify will judge every fix with them.

Ignores Snyk itself reports as accepted are honored and listed as coverage exclusions. Ignores the change under review adds are not: when a fix is judged, .snyk, .semgrepignore, .trivyignore and .dvalincodeignore are restored to the base commit's version and new inline markers (deepcode ignore, nosemgrep, nosec, NOSONAR, …) are blanked, so a finding that was silenced rather than fixed still fails the fix. A suppression is a risk decision a person makes in its own change, never a repair.

The same judgement applies wherever Dvalin says "verified": the fix loop, dvalin verify, the MCP dvalin_verify_findings tool, single-round --fix --verify, and CI reverify. Those records are dvalin-fix-record/v3, which also fails a repair that hid a finding instead of fixing it — a sibling sink, a deleted vulnerable file, deleted tests or assertions. Outside a git repository, where the change cannot be determined, records stay v2 and say that evasion was not evaluated.

Let it loop until the gate would pass

dvalincode dvalin . --scanners builtin,snyk-code,snyk-oss --until-clean --max-rounds 3 --executor codex

--until-clean turns one fix attempt into a bounded loop. The executor edits; Dvalin re-scans with the same engines and runs the project's checks itself; the executor is sent only what is still wrong — targets still present, findings its change introduced, failing checks with their last lines of output, suppressions it added — and fixes again. Dependency findings go first.

The loop stops, and says which, when every target is gone with nothing blocking introduced and every check passing (verified), when the round budget runs out, when a round leaves the same problems open or the open set stops shrinking (stalled), when the targets need a person — a dependency with no fixed version (not-auto-fixable) — or when the engines or checks cannot run (unverifiable). Suppressions the executor adds are undone before each scan and stay open problems until removed. Every outcome issues a fix record, and every round is logged under ~/.dvalincode/security/fix-loops/; dvalin loop-stats summarizes those logs — how often loops reach verified, the median and p90 rounds to green, which stop rule fired, and what was still open when a loop stalled (--json, --since, --executor to slice it).

Rounds are kept short without changing what decides them. The engines run side by side, so a scan costs the slowest engine rather than the sum. Between rounds, the engines that analyse one file at a time (Dvalin Built-in, Semgrep) rescan only the files the change touched and the files the targets are in; the others always scan the whole tree. That narrowed scan only steers the executor: any round that would stop the loop is re-judged on a full scan of the same tree, and only the full scan decides the outcome and goes into the record. --full-rescan scans everything every round, and loop-stats reports scan time per round and how often a full scan changed the decision.

For code findings the loop first asks for a reproduction: tests only, which Dvalin runs on the unfixed code and requires to fail — by a failing assertion, not a missing runner or a not-yet-written import. The tests are hashed; the fix must then make them pass unchanged, and that run is a check in the record. A finding no test could demonstrate stops the loop as not-reproduced before anything is fixed: it may be a false positive, and a person triages it. The runner is inferred (vitest, jest, mocha, node --test, pytest, go test) or set with --repro-command 'npx vitest run {files}' or reproduce in dvalin.security.json — never chosen by the executor.

Each fix round also rejects the evasions an agent reaches for when the goal is "the scanner went quiet": moving the sink to a sibling (eval → new Function, exec → spawn with shell: true), deleting the vulnerable file, deleting tests, removing assertions. In practice the scanner alone is fooled by the first one — the rule stops matching — and the reproduction test and the evasion check are what catch it.

Keep it on today's main, and close it in CI

dvalincode dvalin . --scanners builtin,snyk-code,snyk-oss --until-clean \
  --rebase-onto origin/main --executor codex --draft-pr --sign-key ci.key

With --rebase-onto the loop fetches and rebases the fix onto upstream before it starts and after every round, then judges it there. Conflicts go to the executor — the conflicted files only, with "do not run git" — and Dvalin continues the rebase itself; markers left in a file are a conflict not resolved. If they cannot be resolved the rebase is aborted, the tree is left as it was, and the loop stops as rebase-conflict. After each rebase the baseline is re-scanned on the new base, so a finding a teammate landed on main is not blamed on the fix.

--draft-pr commits the record at .dvalin/fix-record.json, and docs/examples/dvalin-fix-loop.yml re-executes it on the pull request with reverify: true: base and head re-scanned, checks re-run, and the reproduction re-run with the fix reverted in place (it must fail) and restored (it must pass) — then signed with the CI key.

For an incremental “no new high-risk findings” gate, commit the policy and baseline with the repository:

dvalin init
dvalin baseline
dvalin scan

This creates dvalin.security.json and .dvalin/baseline.json. Suppressions require a reason and may have an owner and expiry date. Scan output is a versioned envelope with a deterministic gate result and a resumable workflow ID.

Or put it on every pull request — nothing to install at all

# .github/workflows/security.yml
permissions:
  contents: read
  security-events: write
steps:
  - uses: actions/checkout@v5
    with:
      fetch-depth: 0        # so the scan can reach the base commit
  - uses: arthurpanhku/dvalincode@v0.18.0
    with:
      fail-on: high
      diff: true            # only report on what this PR changed

Findings land inline on the pull request diff and in your Security tab. No API key, no secrets, no model — the scan is deterministic and local to the runner. Full example →

diff: true reports only on lines the pull request changed, so the gate blocks what this change adds instead of everything the repository already carried. That is what makes the check adoptable on a codebase that was not clean to begin with. Drop it to scan the whole repository.

Every comment states what the scan covered — complete, partial, or unknown — beside the result, because "no findings" from a run where half the engines were missing is not the same answer as "no findings" from a complete one.

And publish the proof next to the diff

If your pipeline produced a fix record, hand it to the same action:

  - uses: arthurpanhku/dvalincode@v0.18.0
    with:
      fix-record: fix-record.json

The runner re-derives the record from the file alone — recomputing its hash and re-deriving its verdict from its own evidence — and posts the result on the pull request. A record that was edited after it was issued fails here, and fails the job.

Re-derivation proves the record is self-consistent. It cannot prove the record was issued by anyone who actually ran anything: every field in it, the exit codes included, is something a forger could write, and a self-consistent forgery re-derives. Two things close that gap — re-execute the claim on the runner, and sign what the runner observed.

🔏 Verified Fix Record
✅ ce504a995395 · VERIFIED · scan-and-checks
- repaired by claude-code — recorded, and not consulted for this verdict
- targets: 1 before → 0 remaining
- coverage: complete → complete
- introduced: none (gate high/new)
- outcome: verified
- ✓ test: `npm run test` (exit 0)
- audit chain: verify-eeb1bae7 @ 80881867270d

A repair that regressed says so in the same place, and fails the job with it:

❌ 916e2eeaf065 · NOT VERIFIED · scan-and-checks
- introduced: 1 finding(s) the first scan did not report (gate high/new)
  - critical dvalin/sql-injection — src/db.ts:31
- outcome: regressed

Re-execute it on the runner, and sign what you saw

  - uses: actions/checkout@v5
    with:
      fetch-depth: 0              # the base commit has to be reachable
  - run: npm ci                   # the project's checks run on the runner
  - uses: arthurpanhku/dvalincode@v0.22.0
    with:
      fix-record: fix-record.json
      reverify: true
      signing-key: ${{ secrets.DVALIN_SIGNING_KEY }}   # optional

With reverify: true the claimed record contributes only which targets it says it fixed and who the executor was. Everything else is observed again:

  • the base commit is checked out and scanned, so each claimed target must actually have existed there — a record cannot take credit for removing a finding nobody had;
  • the head is scanned, so "gone" is observed on the runner;
  • the project's checks are run on the runner, with the checks, gate and scanners read from the base commit's dvalin.security.json — a pull request that relaxes its own gate or deletes its own checks is judged under the rules it is trying to change;
  • a fresh record is issued from those observations, and the job fails unless it verifies.

With signing-key the fresh record is signed (Ed25519, over its hash). The key is taken into memory and removed from the environment before the project's checks run, so the code under review cannot read it. Downstream — a release job, another repository, an auditor — can then require that signature:

dvalin keygen --out ci                          # once; store ci.key as a secret, publish ci.pub
dvalin verify-fix record.json --trusted-key ci.pub

A record that is unsigned, or signed only by a key you did not name, fails under --trusted-key. A valid signature says which key vouched for this record; whether that key deserves trust is your decision, and the reason to trust a CI key is that the CI job re-executed the verification rather than copying it. Locally, dvalin reverify <record> --base origin/main runs the same re-execution, and dvalin sign-fix signs a record that re-derives. FVP-1 §4a, §5a →

Or let your agent call it

If an agent is writing the code, something other than that agent has to check it. DvalinCode is an MCP server, so any agent that speaks MCP can:

claude mcp add dvalin -- npx -y dvalincode mcp-serve --workspace .

One command configures the editor you actually use:

npx dvalincode mcp-install cursor        # .cursor/mcp.json
npx dvalincode mcp-install vscode        # .vscode/mcp.json
npx dvalincode mcp-install claude-code   # .mcp.json

The formats differ in a way that fails silently — VS Code keys its servers under servers, Cursor under mcpServers — so the command writes the right one and merges into whatever is already there. Editors and MCP →

dvalin_scan accepts diff: "uncommitted", which reports only on what the agent just wrote rather than everything the repository already carried — the difference between a usable answer and a wall of pre-existing findings. It never runs a model, edits the target workspace, or persists Dvalin state, so clients can allow the preview by default. When a finding will be repaired, the agent explicitly calls dvalin_begin_verification to record a small local workflow; it can then retrieve the finding by fingerprint and request an independent re-scan through dvalin_get_finding and dvalin_verify_findings.

That last one is the point: an agent that has just written a repair can ask for an independent verdict on it. Dvalin re-scans, runs the project's own checks itself, and returns a Verified Fix Record — what was targeted, what remains, what the repair introduced that was not there before, the gate the verdict was reached under, which commands ran and the exit codes Dvalin observed, and how much of the codebase was actually covered. Whoever wrote the repair is recorded and never consulted. dvalin_verify_fix re-derives such a record offline, so the reviewer receiving it does not have to trust the tool that issued it. FVP-1 → Responses include MCP structuredContent; scanner readiness is available through dvalin_list_scanners. The same server exposes dvalin_run_task as an optional implementation helper, plus session and audit evidence tools.

Every client below has been driven to a real tool call rather than only a handshake — which client, which version, and on what date is a table rather than a sentence, because hand-written version numbers go stale quietly. Integration support ↓ · Agent integrations →

The repository also contains one dual Codex/Claude plugin payload with both native manifests, the shared security-gate skill, and the local MCP server configuration. Installing it makes the gate discoverable from task context instead of requiring every developer to remember a scan prompt. Codex honors the scan's read-only MCP annotation. Claude Code requires one explicit MCP permission by design; the plugin documents the exact scan-only allow rule instead of asking users to bypass all permissions.

Wherever you already work

One server, reached the way each tool expects:

HarnessHow Dvalin reaches it
Claude Codedual plugin or claude mcp add · standalone skill
Codexdual plugin or codex mcp add · SARIF interop with Codex Security
Cursordvalincode mcp-install cursor
VS Codedvalincode mcp-install vscode · extension for Problems, coverage/gate status, and offline VFR verification — built, not yet published
Windsurf · Zedstdio MCP through their own settings — server command
Any MCP clientMCP registry: io.github.arthurpanhku/dvalincode
GitHub ActionsMarketplace action — findings inline on the pull request diff
Any CIdvalin scan . --fail-on high, SARIF out for code scanning

The MCP config formats are not interchangeable — VS Code keys its servers under servers, Cursor under mcpServers, and the wrong one fails silently — so mcp-install writes the right shape and merges into whatever is already there. Editors and MCP →

Or interoperate with Codex Security

Codex Security can export a completed, sealed scan as SARIF. Import that portable projection without coupling Dvalin to Codex Security's private state directory:

DVALIN_CODEX_SCAN_DIR=/tmp/codex-security-results
npx @openai/codex-security scan . --output-dir "$DVALIN_CODEX_SCAN_DIR"
npx @openai/codex-security export "$DVALIN_CODEX_SCAN_DIR" \
  --export-format sarif --source-root "$PWD" --output /tmp/codex-security.sarif

dvalin import /tmp/codex-security.sarif .
dvalin scan . --fail-on high

The import creates stable Dvalin remediation cases; --no-persist validates the handoff without changing the backlog. Keep Codex Security's original manifest, findings, and coverage artifacts together—Dvalin imports the SARIF finding projection but does not rewrite its sealed bundle or reinterpret its coverage. Integration guide →

Then let it fix what it found

dvalincode dvalin . --fix --verify --draft-pr

This step does use a model — your model, any OpenAI-compatible endpoint. It prepares focused repairs in an isolated worktree, runs your tests, and requires a clean re-scan before anything can proceed to a draft PR. It never auto-merges, and a clean scan is never treated as proof that the code is safe.

This animation is made from the real application, not a mock. The input is an Apache-2.0-licensed example adapted from OWASP NodeGoat, whose contribution route evaluated user-controlled text.

🛡️ What that run actually did

Dvalin turns open-source scanner evidence into a controlled scan → fix → test → re-scan → draft-PR workflow. Here is the run in the animation above, measured:

Real NodeGoat-derived runBeforeAfter Dvalin remediation
Security health (triage heuristic)22 / 100 · F100 / 100 · A
Findings10 (eval across 3 rules, 2 engines)0
Tests2 passing3 passing, including an injection regression test
Scanner fleet4 / 4 completed4 / 4 completed

The scanning and hardening control plane uses open-source components:

  • Semgrep CE and its community rules for semantic SAST.
  • Trivy for filesystem vulnerabilities, secrets, and misconfiguration.
  • OSV-Scanner with the open OSV database for dependency vulnerabilities.
  • DvalinCode's MIT-licensed built-in rules, remediation orchestration, test and re-scan gates, plus SARIF 2.1 import for other compatible scanners.

The scanners find and rank evidence. The configured model proposes source changes; DvalinCode constrains that work, records the diff, runs project tests, re-scans the changed tree, and keeps PR publication explicit. It does not auto-merge and it does not claim that a clean scan proves the absence of bugs. Choose an open-weight model through Ollama if the repair-proposal step must also stay fully local and open; hosted model licensing depends on the provider.

You can still prove what the agent did after the fact:

dvalincode report verify    # re-derive the hash chain of the last run's audit log

Shortened here. Read the whole README on GitHub.

Signals

GitHub stars
120
Forks
14
Last commit
Oct 2026
Weekly downloads
85
Advanced
Delivery
dvalincode MCP server → your Ahel connector (mcp.ahel.ai) → your AI.
Item type
mcp-server
Key
io-github-arthurpanhku-dvalincode
Source
github.com/arthurpanhku/dvalincode