dshbase

插件目录 / Security / dsh-route-fence-linter

dsh-route-fence-linter

已验证 · 实测可装 Vladimir-Kryshchenko

✓ 持续维护

查看 GitHub ↗ ← 返回插件目录

0Stars
0Forks
0未关闭 issue
—语言
2026-08-22最近推送
跨平台平台

功能简介

Static linter for DeepSeek Harness plugin HTTP routes: every webServer route bypasses the /api gateway's trust check and must pin the Host to loopback itself. PASS/WARN/FAIL per route, as a CLI and a dsh tool.

✅
我们的评价
可用 — 实测通过,早期项目

Static linter for DeepSeek Harness plugin HTTP routes: every webServer route bypasses the /api gateway's trust check and must pin the Host to loopback itself. PASS/WARN/FAIL per route, as a CLI and a dsh tool. 实测能干净安装、正常启动。早期项目,但功能可用。

「已验证」表示我们的自动化 CI 在干净 profile 里实际执行了 dsh plugin add 并启动成功——仅此而已。功能描述与版本兼容性均为作者声明。这不是安全审计,也不代表对第三方代码的背书。

README

dsh-route-fence-linter

Every plugin route registered on webServer is dispatched by longest-prefix
match
, ahead of the host's /api gateway — so it never passes the gateway's
unified trust check. Each plugin author must ship their own browser-trust
fence, and most do not. This linter finds the ones that did not.

Usage

dsh-route-fence scan                       # $DSH_HOME/profiles/web
dsh-route-fence scan /path/to/profile

Exit codes: 0 clean · 1 at least one FAIL · 2 usage/IO error.

Verdicts

Verdict Meaning
PASS The handler gates on a fence that pins the Host before Origin handling.
WARN Either: inside the fence, Origin / sec-fetch-site is read before the Host is pinned; or the registered routes are defined outside the scanned package, so their fence could not be read. Confirm by hand.
FAIL One of: no Host check anywhere; a fence that compares Origin to Host but never pins the Host to loopback (bypassable by DNS rebinding — the attacker controls both headers and they match); or a fence that exists but this handler never calls.

Bundles whose sources cannot be read are reported as SKIP and counted in the
summary — a skipped bundle is not a clean bundle.

registerFallback is checked too, and reads worse than any route when
unfenced: the fallback seat answers every request no named route matched,
so one unfenced fallback is a wider surface than a whole prefix tree.

Scan scope

The scan covers what a package publishes (its package.json files field);
a package that declares no files is scanned whole. Anything excluded is named
in the output as a note line, never dropped silently.

Scoping by directory name was tried and reverted: skipping anything called
tests/ or examples/ would have let a plugin hide an unfenced route in a
directory with that name and be reported clean. Publication scope cannot be
gamed that way — the loader can only import what was installed.

The rebinding-bypassable shape is graded FAIL, not WARN: it was found live
in a real, widely-installed plugin and confirmed exploitable against a running
profile — a request with Host: evil.example and Origin: http://evil.example
passed the check and executed a state-changing method.

The fence this checks for

The shape the host itself uses (and dsh-better-sidebar/src/trust-fence.ts
copies): pin the Host header to loopback or a configured trusted authority
first, refuse cross-site fetch markers, and only then compare Origin.
Comparing Origin before pinning the Host is not a fence.

Routes defined in another file

A route registered as routes.map(r => webServer.register(r)), where routes
came from buildWebRoutes() in src/web/routes.ts, has no fence anywhere near
its registration — the fence is one module away. Grading such a route by the
text around the registration produced a false FAIL on real plugins, so the
linter follows the value instead.

When the registered argument is not an inline object — a bare identifier, a
factory call build(...), a spread, or the element parameter of a .map /
.forEach / .flatMap over one — the linter resolves that name to where the
handlers are actually written and grades them there. Each handler in the traced
definition is graded on its own; one unfenced handler fails the whole
registration, so a factory that fences four routes and forgets the fifth is a
FAIL, not a PASS.

The walk is fail-closed at every step. It reports PASS only when it found real
handler bodies that gate on the fence. Routes coming from a bare-specifier
import are WARN ("outside the scanned package — check by hand"), never PASS;
so is a relative import that resolves to nothing scanned. Anything else it
cannot establish — a handler: that is a bare function reference, a factory it
cannot find, a chain longer than three module hops — falls back to the previous
FAIL. Import cycles terminate on a visited-set of (file, name) pairs.

Specifier resolution covers what dsh plugins actually write: relative paths
with .ts / .mts / .tsx / .js / .mjs / .cjs or no extension, the
.js-spelled-for-a-.ts-file convention, and directory imports resolved
through index.<ext>. It is not a Node resolver: package.json exports,
imports (#alias), tsconfig paths, and workspace links are not followed —
they resolve to nothing scanned, which is WARN.

Limits

Heuristic over source text, not dataflow analysis. A fence is identified as the
smallest function body that both references the Host and constrains it; a
handler counts as gated when it calls that fence, receives it as a value, or
performs the check inline — cross-file tracing holds a traced handler to
exactly that same standard.

Still out of reach: a fence installed by wrapping (register(withFence(route))
where the wrapper is what checks), routes assembled at runtime from
configuration, handlers reached only through a bare-identifier reference, and
route sources behind a non-relative specifier. These read WARN or FAIL, never
PASS. The linter also checks that a fence is present in a handler, not that
it is textually the first statement.

Report false verdicts; they are bugs — a false PASS most of all.

安装

🧩 让 Agent 自动装(推荐)

装一次目录插件,之后本站所有插件都能让 DeepSeek Harness 自动找、自动装:

dsh plugin add dshbase-catalog

然后对 agent 说「帮我装 dsh-route-fence-linter」,它会在目录里找到并自动安装。文档:dshbase-catalog · 已验证场景包。

该插件是 GitHub 源码(未发 npm)——直接从仓库装:

Web profile:

dsh plugin --profile web add github:Vladimir-Kryshchenko/dsh-route-fence-linter

Headless(CLI)profile:

dsh plugin --profile headless add github:Vladimir-Kryshchenko/dsh-route-fence-linter

实测报告

验证通过:从 GitHub 源码完成 L1 安装 + L2 加载 + L3 运行(dsh 0.1.0-rc.6)。

安全:尚未扫描——我们的每日静态扫描将很快覆盖它。

分享徽章

Security 里更多

浏览全部 7819 个插件 →