技能 安全 扫描
- 作者仓库星标 430
- 作者仓库 aeon
${var} — a SKILL.md path, a skill name (e.g.
token-movers), or a directory. Empty = full corpus scan.
Today is ${today}. Audit the codebase for security risks in skill instructions, CI workflows, and companion scripts before they run.
Threat categories
Files instruct Claude Code and GitHub Actions runners to take actions. Adversarial or sloppy files can:
- Shell injection — unquoted variable expansion,
eval, backticks,$(...)in bash blocks - Secret exfiltration — env vars or file contents piped into outbound HTTP requests
- GitHub Actions script injection — user-controlled template expressions (
${{ github.event.* }}, PR titles, issue bodies, incoming messages) interpolated directly intorun:blocks (see the 2026-04-11messages.ymlincident inarticles/workflow-security-audit-2026-04-11.mdfor the canonical pattern and fix) - Path traversal — access files outside repo via
../..chains or absolute paths - Prompt override — instructions in fetched content or skill bodies attempting to make the agent disregard prior guidance, switch persona, or act on new "system" rules
- Destructive commands — irreversible ops like recursive deletes from root, device writes, forced pushes to main
- Obfuscation (2026 additions) — zero-width Unicode (U+200B, U+FEFF), bidi override (U+202E / Trojan Source), base64-decoded payloads,
fromCharCode, hex-escaped command strings, webhook SSRF hosts (ngrok, interact.sh, webhook.site, burpcollaborator, pipedream, requestbin)
Coverage
Scan every run:
skills/*/SKILL.md(primary)skills/*/*.shandskills/*/*.py(companion scripts that skills invoke).github/workflows/*.yml(CI — especiallyrun:blocks referencing${{ ... }})scripts/*.sh(repo-level scripts)
When ${var} is set:
- If it matches an existing SKILL.md path (absolute or relative) → scan that file only
- Else if a directory exists at
skills/${var}/→ scan everything under it - Else if it looks like a bare skill name and
skills/${var}/SKILL.mdexists → scan that file - Else abort with
ERROR: scope not found for var=${var}
Inputs and state
| Path | Purpose |
|---|---|
skills/skill-security-scan/scan.sh |
Raw regex scanner (HIGH/MEDIUM/LOW pattern library) |
skills/security/trusted-sources.txt |
GitHub owners/repos whose skills get format-only scans |
skills/security/scan-baseline.yml |
Human-reviewed-as-safe suppressions (bootstrap if missing) |
memory/state/security-scan.json |
Prior scan snapshot — used for delta |
memory/issues/INDEX.md |
Open/resolved issue index (HIGH findings file here) |
articles/security-scan-${today}.md |
Report output (only written if there are findings or a delta) |
Baseline file format
skills/security/scan-baseline.yml:
# Each entry suppresses a specific (file, line_range, pattern) match that a human has reviewed.
# Format:
# - file: <path>
# pattern: <regex pattern from scan.sh HIGH_PATTERNS/MEDIUM_PATTERNS/LOW_PATTERNS>
# lines: "15-25" # optional line range; omit to suppress across whole file
# reason: "documentation in threat model section"
# reviewed_by: "aaronjmars"
# reviewed_at: "2026-04-20"
suppressions: []
Seed suppressions at bootstrap with the self-documenting matches that we already know are false positives:
skills/skill-security-scan/SKILL.md— all prompt-override pattern matches inside the "Threat categories" section (documentation, not payload)skills/security-digest/SKILL.md— any curl/token pattern inside fenced code blocks showing example usage
Steps
Read memory. Read
memory/MEMORY.mdand today'smemory/logs/${today}.md(create if missing) for context.Bootstrap baseline. If
skills/security/scan-baseline.ymldoes not exist, create it with the seed suppressions listed above and recordBASELINE_BOOTSTRAPPEDin the exit status.Resolve scope per the
${var}rules above. Log the chosen scope.Preflight scanner. Verify
skills/skill-security-scan/scan.shis present and executable. If missing (sandbox edge case), fall back to inline Grep using the same HIGH/MEDIUM/LOW pattern library defined inscan.sh— never silently skip.Run scanner in JSON mode — invoke
scan.sh --json(or--all --jsonfor the full corpus) and capture the structured output:[{skill, status, file, high, medium, low}, ...]. Do not parse stderr into findings.Trusted-source filter. Load
skills/security/trusted-sources.txt. For each scanned file, check if the skill directory has anorigin:field in its frontmatter, or fall back to the repo's git remote. If the source is trusted (owner or owner/repo match), downgrade to format-only validation: verify frontmatter hasname,description,tags, and avarkey — emit no HIGH/MEDIUM/LOW findings for trusted sources, only format errors.Code-fence downgrade. For each non-trusted finding, re-read the file around the finding's line. If the line is inside a fenced code block (between
```markers in a Markdown file, or inside arun: |/script: |YAML block in a workflow file that is clearly an example, not an executable step), downgrade severity by one tier (HIGH → MEDIUM, MEDIUM → LOW, LOW → drop). Never downgrade inside actualrun:steps in real workflow files — those execute.Apply baseline suppression. Drop any finding whose (file, pattern, line) tuple is in
skills/security/scan-baseline.yml.Compute delta against
memory/state/security-scan.json(previous run's finding set, keyed bysha256(file+line_content+pattern)):- NEW — findings present now but not last run
- RESOLVED — findings present last run but gone now
- PERSISTENT — findings in both runs (not re-notified, but still counted)
File/close issues in
memory/issues/:- For each NEW HIGH finding (post-suppression): create
memory/issues/ISS-{next_id}.mdwith YAML frontmatter (id,title,status: open,severity: high,category: quality-regression,detected_by: skill-security-scan,detected_at: ${today},affected_skills) and append a row toINDEX.mdunder## Open. - For each RESOLVED finding that corresponds to an open ISS filed by
skill-security-scan: setstatus: resolved,resolved_at: ${today}, move the row from## Opento## ResolvedinINDEX.md. - Do NOT file issues for NEW MEDIUM or LOW findings — those live in the article report only.
- For each NEW HIGH finding (post-suppression): create
Write the report to
articles/security-scan-${today}.mdonly if there are any NEW, RESOLVED, or current HIGH findings. Structure:# Security Scan — ${today} **Verdict:** [CLEAN | ATTENTION | DEGRADED] **Scope:** [full corpus | ${var}] **Counts:** N files scanned · H HIGH · M MEDIUM · L LOW · X new · Y resolved since last scan ## Needs attention (NEW high-severity this run) For each: file:line, pattern that matched, one-line remediation snippet (see table below). ## Resolved since last scan List of findings that disappeared — good for confirming fixes. ## Persistent findings (unchanged) Count per severity; full list only in the appendix. ## Per-file results Table: file, status (PASS/WARN/FAIL), HIGH count, MEDIUM count, LOW count. ## Appendix — all current findings Full structured dump.Remediation snippets. For each HIGH finding, attach a one-line fix hint keyed off the pattern. Map (non-exhaustive — extend as new patterns are added to
scan.sh):Pattern category Remediation Shell eval / backticks / $(...)with variableQuote the variable; prefer ${VAR}with explicit quoting; replaceevalwith a functioncurl/wgetwith an env var in the URL or bodyMove secret into a pre-fetch script (see CLAUDE.mdSandbox section); never interpolate secrets into shell-block strings${{ github.event.* }}inside arun:blockRebind the value to an env:key first, then read$_SAFE_NAMEfrom the shell (seearticles/workflow-security-audit-2026-04-11.md)Path-traversal sequence Validate input against skills/*/or explicit allow-list; reject absolute pathsPrompt-override phrasing If the string is documentation, add a baseline suppression entry; if it's a payload, delete it Recursive delete rooted at /or~Scope to $REPO_ROOTor a specific subdir; never take a variable as the delete rootForce-push to main Remove the option or gate behind explicit human dispatch Obfuscation (zero-width / bidi / base64-decode pipe) Delete unless there's a documented, reviewed reason Persist state. Write the full current finding set to
memory/state/security-scan.jsonso the next run can compute delta. Include{generated_at, scope, findings: [{file, line, pattern, severity, fingerprint}]}.Notify via
./notifyonly when there is something new for the operator:- If any NEW HIGH finding → one paragraph summary naming affected skill(s), finding count, and path to the report.
- If any RESOLVED HIGH finding (but no new HIGH) → short "Resolved: X HIGH findings cleared since last scan."
- If only MEDIUM/LOW changes → skip notification (report is written, operator reads on demand).
- If no findings and no delta → skip notification; emit
SECURITY_SCAN_OKto stdout so heartbeat can log it.
Log to
memory/logs/${today}.mdwith an### skill-security-scansection: scope, exit status code, counts by severity, new/resolved counts, PR/issue IDs filed, report path.
Exit status codes
Emit exactly one to stdout (on its own line) before normal output:
SECURITY_SCAN_OK— no findings after suppression, no deltaSECURITY_SCAN_NEW— at least one NEW HIGH findingSECURITY_SCAN_RESOLVED— no new HIGH findings, but at least one was resolvedSECURITY_SCAN_NOCHANGE— findings exist but identical to last runSECURITY_SCAN_BOOTSTRAPPED— baseline file was just created; this run writes initial stateSECURITY_SCAN_ERROR— scope unresolvable, scanner missing, or write failure
Constraints
- Never auto-delete a finding from
scan-baseline.yml. Suppression is a human decision; the skill only adds seed entries on first bootstrap. - Never file an issue for a finding that is already represented by an open ISS (match by fingerprint — file+line+pattern).
- Never change
scan.sh's pattern library from inside this skill. Pattern evolution happens in a separate, reviewed PR. - Never notify on a pure no-op week. Silence is correct when nothing has changed.
- Treat trusted-sources downgrades as opt-in only — never trust a source not explicitly listed.
Sandbox note
This skill reads local files and shells out to scan.sh; no network calls required. If scan.sh is unavailable, perform the scan inline using Grep with the same pattern library — never silently skip. The ./notify call is covered by the standard post-processor (see CLAUDE.md Sandbox section).
- 流狐分类
- 安全 · dev
- 作者声明 Agent
- 未找到明确声明;不据此推断已兼容或已测试
- 静态检查
- 94 / 100 · 启发式扫描,不代表运行安全
- 作者 / 版本 / 许可
- @aaronjmars · 未声明 license
- 流狐 Token 估算
- 低消耗
- 流狐接入估算
- 需简单配置
- 是否需要外部 API Key
- 未发现要求
- 检测到的系统要求
- 未声明
- 底层运行要求
- 未声明
- 检测到的文件与系统行为
-
- 只读
- 允许写入 / 修改
- Shell 执行
- 读取环境变量
- 检测到的网络行为
- 允许外网请求
- 安装命令数
- 无(仅作为资料)
档案由构建时根据 SKILL.md 与安装命令自动衍生,可能与作者实际意图存在差异。
作者没有在当前 SKILL.md 中定义固定输出样例。 Files instruct Claude Code and GitHub Actions runners to take actions. Adversarial or sloppy files can: Shell injection — unquoted variable expansion, eval, backticks, $(...) in bash blocks Secret exfiltration — env vars or file contents piped into outbound…
Scan every run: skills//SKILL.md (primary) skills//.sh and skills//.py (companion scripts that skills invoke)
Path · Purpose skills/skill-security-scan/scan.sh · Raw regex scanner (HIGH/MEDIUM/LOW pattern library) skills/security/trusted-sources.txt · GitHub owners/repos whose skills get format-only scans
skills/security/scan-baseline.yml: Seed suppressions at bootstrap with the self-documenting matches that we already know are false positives: skills/skill-security-scan/SKILL.md — all prompt-override pattern matches inside the "Threat categories" section…
Read memory. Read memory/MEMORY.md and today's memory/logs/${today}.md (create if missing) for context. Bootstrap baseline. If skills/security/scan-baseline.yml does not exist, create it with the seed suppressions listed above and record BASELINEBOOTSTRAPPED…
Emit exactly one to stdout (on its own line) before normal output: SECURITYSCANOK — no findings after suppression, no delta SECURITYSCANNEW — at least one NEW HIGH finding
<!-- autoresearch: variation B — sharper output: delta tracking + issue filing + code-fence-aware suppression + baseline + remediation snippets + expanded coverage (workflows, companion scripts) -->
> **${var}** — a SKILL.md path, a skill name (e.g. `token-movers`), or a directory. Empty = full corpus scan.
Today is ${today}. Audit the codebase for security risks in skill instructions, CI workflows, and companion scripts before they run.
## Threat categories
Files instruct Claude Code and GitHub Actions runners to take actions. Adversarial or sloppy files can:
- **Shell injection** — unquoted variable expansion, `eval`, backticks, `$(...)` in bash blocks
- **Secret exfiltration** — env vars or file contents piped into outbound HTTP requests
- **GitHub Actions script injection** — user-controlled template expressions (`${{ github.event.* }}`, PR titles, issue bodies, incoming messages) interpolated directly into `run:` blocks (see the 2026-04-11 `messages.yml` incident in `articles/workflow-security-audit-2026-04-11.md` for the canonical pattern and fix)
- **Path traversal** — access files outside repo via `../..` chains or absolute paths
- **Prompt override** — instructions in fetched content or skill bodies attempting to make the agent disregard prior guidance, switch persona, or act on new "system" rules
- **Destructive commands** — irreversible ops like recursive deletes from root, device writes, forced pushes to main
- **Obfuscation (2026 additions)** — zero-width Unicode (U+200B, U+FEFF), bidi override (U+202E / Trojan Source), base64-decoded payloads, `fromCharCode`, hex-escaped command strings, webhook SSRF hosts (ngrok, interact.sh, webhook.site, burpcollaborator, pipedream, requestbin)
## Coverage
Scan every run:
- `skills/*/SKILL.md` (primary)
… 作者原文负责流程事实;流狐只索引当前章节、要点、文件与命令。
章节 -> Threat categories → Coverage → Inputs and state → Baseline file format → Steps → Exit status codes
要点 -> ${var} · Shell injection · Secret exfiltration · GitHub Actions script injection · Path traversal · Prompt override · Destructive commands · Obfuscation (2026 additions)
文件/命令 -> token-movers · eval · $(...) · ${{ github.event. }} · run: · messages.yml · articles/workflow-security-audit-2026-04-11.md · ../..
内容 SHA-256 -> 4b2c54141022
方法与流程
适用与边界
原文中的明确线索
token-movers、eval、$(...)、${{ github.event. }}、run:、messages.yml、articles/workflow-security-audit-2026-04-11.md、../..