DeepSHPlugins
secret-guard 專案預覽
GITHUB / COMMUNITY PROJECT

secret-guard

安全與運維TypeScriptMIT

攔截 agent 對敏感檔案(.env、憑據、金鑰材料)的讀寫,對工具結果中洩露的機密形狀內容做掩碼兜底,記錄審計日誌,並提供永不輸出原始值的 sg_* 安全檢查工具。

0 StarsJohnXu22786 更新時間 2026-08-17
訪問 GitHub 倉庫
secret-guard 專案預覽

專案介紹

中文

攔截 agent 對敏感檔案(.env、憑據、金鑰材料)的讀寫,對工具結果中洩露的機密形狀內容做掩碼兜底,記錄審計日誌,並提供永不輸出原始值的 sg_* 安全檢查工具。

English

Blocks agents from reading or writing sensitive files (.env, credentials, key material), masks leaked secret-shaped values in tool results, keeps an audit journal, and exposes safe sg_* inspection tools that never print raw values.

專案資訊與收錄訊號

快照 2026-09-11
作者 / 組織
JohnXu22786
倉庫
JohnXu22786/secret-guard
GitHub Stars
0
Forks
0
開發語言
TypeScript
許可證
MIT
最近推送
2026-08-17
Manifest
資料中檢測到宣告

公開倉庫不自動等於可商用;許可證與當前相容性以原專案文件為準。Manifest 和資料評分不構成安裝安全認證。

資料評分

36/ 100
維護活躍
83
文件與實用資訊
85
生態關注度
0
安裝便捷訊號
80
專案宣告訊號
100

README 含完整安裝與使用說明。

評分來自已有資料快照的五維模型,僅作瀏覽參考,不是實際使用評測或安全檢測結論。

安裝與使用

所有專案資料與許可均以對應 GitHub 倉庫為準。

dsh plugin --profile web add github:JohnXu22786/secret-guard

複製命令不會自動執行。請先閱讀倉庫文件,確認環境要求與許可證。

專案截圖

倉庫 README

GitHub 原文

已儲存的倉庫文件快照;文件抓取時間未記錄,可能與上方目錄介紹處於不同版本。安裝前請核對當前倉庫說明。

儲存庫檔案保留原文,指令與授權文字不作自動改寫。

简体中文

secret-guard

A security plugin for the DeepSeek Harness (dsh): it intercepts reads and writes of sensitive files (.env, credentials, key material, etc.) by the agent's file tools before they execute, preventing API keys and other secrets from leaking into the conversation context; it also applies a content-masking fallback on tool results so that even content that slips past the interception gets scrubbed.

  • Zero build: loads as pure TypeScript source (dsh loads .ts via Node's native strip-only type stripping, so the source must not use syntax unsupported by strip, such as parameter properties — this repo complies and ships an npm run smoke:strip smoke check); self-contained, with only @deepseek-ai/dsh-tools (tool definitions) and schemastery (config validation) as runtime dependencies.
  • Interception layer: tools/pre-execute waterfall event (short-circuits before the tool body runs).
  • Fallback layer: tools/post-execute waterfall event (shape-recognition masking of result content).
  • Companion sg_* safety inspection tools: they only return key names, line numbers, shapes, booleans, and HMAC fingerprints — they never return raw values.
  • Audit journal: JSONL append-only, rotated by size; rule files hot-reload (automatic polling + manual sg_reload).

Directory Structure

secret-guard/
  package.json         # dsh.bundle.patch declaration; main points at the source
  cordis.patch.yml     # bundle patch layer (plugin line: id / name / config)
  src/
    index.ts           # plugin entry: name / inject / Config / apply
    config.ts          # config schema (schemastery) + validation and defaults
    policy.ts          # rule engine: path normalization, glob compilation, default rule table
    gate.ts            # tools/pre-execute interception listener
    scrub.ts           # tools/post-execute content-masking listener
    inspect.ts         # dotenv parsing, value shape classification, sg_* safety tools
    fingerprint.ts     # HMAC-SHA256 seal key and fingerprints
    journal.ts         # JSONL audit journal + rotation
    watch.ts           # rule-file hot-reload poller
  tests/               # node:test + tsx, runs without compilation
  README.md
  LICENSE

Installing in DSH

dsh plugin --profile demo add github:JohnXu22786/secret-guard

The default configuration takes effect after installation. To remove:

dsh plugin --profile demo remove dsh-secret-guard

Installation and Integration (how dsh loads it)

The plugin follows dsh's Cordis plugin conventions: an ESM module (entry src/index.ts) exporting name / inject / Config / apply(ctx, config), inserted into the plugin tree by cordis.patch.yml as a bundle layer. All registered side effects (event listeners, tool registration, file polling) are reversibly cleaned up by the function returned from apply and by the Cordis context.

# install from this directory into the web profile (equivalent to pnpm link + bundle-layer load)
dsh plugin --profile web add .

# or the headless profile
dsh plugin --profile headless add .

The default configuration is active right after installation. Inspect the plugin tree:

dsh --profile web --dump-config | grep -A 6 secret-guard

Local Development (without installing)

When the plugin is not installed in a profile, the package name in the patch line cannot be resolved from the profile directory, so you need an absolute-path overlay pointing at the source entry. Note that on Windows, Node's ESM does not recognize drive-letter paths (D:/… is treated as a protocol) and requires a file:/// prefix:

# dev-overlay.yml
- insert:
    - id: secret-guard-dev
      name: 'file:///D:/path/to/secret-guard/src/index.ts'
      config:
        maskResults: true
dsh --profile headless --patch D:/path/to/secret-guard/dev-overlay.yml "list the current directory"

Requirements

  • dsh ≥ 0.1.0-rc.6 (the @deepseek-ai/dsh-tools typings match the 0.1.0-rc.6 interface surface)
  • Node.js ≥ 22.19 (or ≥ 24)
  • peer dependencies: @deepseek-ai/cordis, @deepseek-ai/dsh-tools, @deepseek-ai/dsh-llm (types only), schemastery (must be provided by the host profile; dsh plugin add installs peers automatically)

Plugin Interface at a Glance

InterfaceDescription
Manifestdsh.bundle.patch in package.json → cordis.patch.yml
Entrysrc/index.ts: name='secret-guard', inject=['tools'], apply(ctx, config)
Interception eventconsumes tools/pre-execute (returns {kind:'deny', reason} on refusal, short-circuiting the waterfall)
Masking eventconsumes tools/post-execute (returns an accept decision with the replacement content)
Toolsregisters sg_keys / sg_scan / sg_fingerprint / sg_probe / sg_status / sg_reload
Configthe config field of the plugin line (see below), validated by the schemastery Config

Configuration

Configuration lives under the config key of the plugin line in cordis.patch.yml, or can be overridden via the profile's cordis.patch.yml patch:

- insert:
    - id: secret-guard
      name: 'dsh-secret-guard'
      config:
        # custom rules (evaluated before the default rules; first match wins)
        rules:
          - id: my-prod-creds
            match: '**/prod-secrets.yml'
            effect: block            # block | block-read | block-write | allow
            reason: 'production credentials, no access'
        # allowlist (checked before any rules; also supports globs)
        allow:
          - 'tests/fixtures/.env'
          - '**/sandbox.env'
        # tools participating in interception (file-type tools whose args contain file_path / path; includes read_image by default)
        gateTools: [read, write, edit, glob, grep, read_image]
        # also intercept grep whose pattern hits sensitive keywords (env/credential/password…)
        guardSearchPatterns: true
        # content-masking fallback for results
        maskResults: true
        # seal key (HMAC): environment variable first, then a local file (auto-created, 0600)
        sealKey:
          env: SECRET_GUARD_SEAL_KEY
          path: .secret-guard/seal.key
        # audit journal
        audit:
          enabled: true
          dir: .secret-guard/logs
          maxBytes: 1048576   # rotate when a single file exceeds this
          keep: 5             # number of rotation files to keep
        # external rule file (JSON, hot-reloadable): { "rules": [...], "allow": [...] }
        rulesFile: .secret-guard/rules.json
        watchRules: true      # automatic polling hot-reload (~400ms interval)

Relative paths (sealKey.path, audit.dir, rulesFile) resolve against dsh's startup working directory.

Rule Syntax and the Default Rule Table

Rule match uses a .gitignore-like glob:

  • Patterns containing / anchor the full path (**/.aws/credentials matches at any depth); a **/ prefix can match zero directory levels;
  • Patterns without / only match the file name (.env matches .env in any directory);
  • ** spans directory segments, * matches anything within a segment, ? matches a single character; a mid-pattern **/ also matches zero directory levels (foo/**/bar matches foo/bar, gitignore semantics);
  • Matching is case-insensitive (safer for interception rules).

Evaluation order: allow allowlist → custom rules (in declaration order) → built-in default rules (first match stops). The allowlist behaves like allow in rules, but is always checked first.

Match target: both rules and the allowlist operate on normalized paths — forward slashes, drive letter and leading / stripped, .. collapsed (a/../b → b). So write patterns in relative form (e.g., tests/fixtures/.env), without drive letters (C:\…); allowlist entries written as absolute paths will silently fail.

Built-in default rules (the id is the rule field in the journal):

idMatchesEffectDescription
guard-vault**/.secret-guard/**blockThe plugin's own storage (seal key, audit journal)
env-example ~ env-default.env.example / .env.sample / .env.template / .env.dist / .env.defaultallowSafe example files
env-file.envblockMay contain real secrets
env-variant.env.*blockEnvironment-specific secret files
env-suffixed*.envblock-readNon-standard naming (e.g., api.env)
aws-credentials**/.aws/credentialsblock
git-credentials**/.git-credentialsblock
netrc**/.netrcblock
npmrc-auth**/.npmrcblock-readContains registry tokens
pypirc**/.pypircblock-readContains registry tokens
credential-files*credential*blockCredential stores
ssh-rsa / ssh-ed25519 / ssh-ecdsa / ssh-dsaid_rsa etc.block-readSSH private keys (writes allowed, supporting key-generation flows)
key-ext-**.pem *.key *.ppk *.p12 *.pfx *.jks *.keystore *.kdbxblock-readPrivate key / keystore material

Effect semantics: block intercepts both reads and writes; block-read only intercepts read-type tools (read/read_image/glob/grep), writes pass; block-write is the opposite; allow passes everything.

Safety Inspection Tools (sg_*)

These tools are intentionally allowed to read blocked files — that is their whole purpose — but they only return metadata, and no output path ever contains raw values.

ToolPurposeReturns
sg_keysList the keys of a dotenv filekey names, line numbers, empty-or-not, value shape labels
sg_scanValue-shape scaneach key's shape (empty/bool/numeric/jwt/url/hex/base64/opaque) and length
sg_fingerprintDeterministic fingerprint of a single keyfirst 16 hex chars of HMAC-SHA256 (stable under the same seal key)
sg_probeBoolean questions about a single keyboolean results of is-set / is-empty / starts-with / ends-with / contains / matches (regex, executed in a separate worker thread with a timeout so pathological regexes cannot hang the host) / equals (constant-time comparison, no plaintext exchange)
sg_statusView the current policyrule count, allowlist, gated tools, mask/audit toggles; the check parameter trial-classifies any path
sg_reloadImmediately re-read the external rule filereload result (rule count / allowlist count / error message)

Usage examples (agent's perspective):

sg_status { check: ".env" }        # -> block (rule 'env-file')
sg_keys { file: ".env" }           # lists only key names
sg_probe { file: ".env", key: "DB_PASSWORD", op: "equals", value: "candidate" }   # true/false
sg_fingerprint { file: ".env", key: "DB_PASSWORD" }   # 9f2c… stable fingerprint

Content-Masking Fallback (scrub)

Even if interception is bypassed (e.g., cat .env via a bash-type tool, MCP tools, or tools not listed in gateTools), tools/post-execute still runs shape recognition on result text, replacing suspected secrets with [redacted:<type>:<length>], and appends a summary line stating how much was scrubbed.

Capability boundary (important): masking is text-shape matching, not a security boundary. Transforming content before output (cat .env | base64, rev, splitting and rejoining lines, etc.) defeats all shape rules; likewise, symlinks and path aliases like .. are known limitations of string classification (see "Security Notes"). It only stops the most common path — the model reading secrets verbatim into context — and is not a substitute for the interception layer.

Recognized shapes (deliberately conservative, to avoid harming hashes, UUIDs, and ordinary long strings):

  • PEM private key blocks (-----BEGIN … PRIVATE KEY-----)
  • JWTs (eyJ… three segments)
  • Bearer tokens (Bearer <16+ chars>)
  • Known key prefixes: ghp_/gho_/ghu_/ghs_/ghr_, sk-, AKIA…, xox[baprs]-
  • Connection strings with plaintext (scheme://user:pass@host, requires a :password@)
  • key=value assignments (key name containing password/passwd/secret/token/api_key/access_key/client_secret/private_key/auth_key, value ≥ 12 chars and not a doc placeholder like <placeholder>/xxx/example)

Deliberately not masked: bare base64/hex long strings (git commit hashes, UUIDs, etc. have high false-positive rates); they are only handled when they appear in the contexts above.

Audit Journal

Every interception (block), masking (mask), rule reload (reload), and error (error) is written to audit.dir/events.jsonl (JSONL, append-only). When a file exceeds maxBytes it is automatically rotated to events.1.jsonl, events.2.jsonl, …, keeping at most keep files.

Journal contract: values are never recorded — entries only contain timestamp, event type, tool name, path, rule id, effect, shape counts, and message. The journal path itself is covered by the default rule guard-vault.

Rule Hot-Reload

Point rulesFile at a JSON file ({ "rules": [...], "allow": [...] }, same structure as the identically named config fields):

  • With watchRules: true, it polls every 400ms and, after a 300ms debounce on file change (mtime/size), auto-reloads;
  • Or call sg_reload at any time to reload immediately;
  • Successful and failed reloads are both written to the audit journal and shown on the console; a failed load never destroys the currently active rules (the old engine is kept).

Implementation note: polling is used instead of fs.watch because on Windows, deleting a watched directory leaks the event loop's exit eligibility (a platform defect), and rule files are often replaced by editors via rename; polling behaves consistently across platforms and leaks nothing (timers are unref'd).

Security Notes

  • The seal key is the root key for HMAC fingerprints: never commit .secret-guard/seal.key — add it to .gitignore; fingerprints change after moving machines or resetting the key (expected behavior).
  • Keep sealKey.path / audit.dir under .secret-guard/ by default (the built-in guard-vault rule protects them). If you move them elsewhere, configure equivalent interception rules yourself, or the agent may read the key file or audit journal.
  • The default rules are deliberately conservative (prefer over-blocking); when over-blocked, use the allow allowlist for precise exemptions, or verify classification first with sg_status {check: …}.
  • This plugin does not migrate, encrypt, or move any secret files; it only prevents "reading secrets into the model context".
  • Known limitations (design trade-offs): path classification is pure string matching — it does not resolve symlink targets, and .. collapsing only covers the string layer; reading sensitive files through execution-type tools like bash is not constrained by the interception layer (the masking fallback does its best to scrub output). sg_probe's boolean questions are a "value oracle" — in theory, multi-round contains/equals queries could reconstruct a value; it is designed for agents to verify values safely and should not be used in untrusted contexts.

Development and Testing

npm install        # devDependencies only (tsx / typescript / cordis runtime / schemastery)
npm test           # node --import tsx --test tests/*.test.ts (77 test cases, ~5s) + strip-only load smoke test
npm run typecheck  # tsc --noEmit

Test coverage: rule engine (normalization/glob/default table/allowlist/custom rule precedence), dotenv parsing, fingerprints and seal keys, masking patterns and false-positive control, the interception decision matrix (including search tools and the keyword guard), audit rotation, and integration tests driving the waterfall events with a real Cordis context (including end-to-end hot reload).

License

This project is released under the MIT License.

使用前核對

安裝步驟、相容版本與授權範圍以原專案當前文件為準。Stars 和評分是快照中的導航訊號。

安裝與排錯 · 專案問題反饋

同類專案