Skip to content
Back to skills

Skill Security Scan

ASecurity

Audit skills, workflows, and companion scripts for injection, exfiltration, traversal, and prompt-override risks with delta tracking, baseline suppression, issue filing, and per-finding remediation

  • 6 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 5, 2026
testingrustgoshellbashexpressgitsecuritydocumentation

Works with

  • claude code

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned September 5, 2026

npx -y skills add anajuliabit/aeon --skill skill-security-scan --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Skill Security Scan?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Skill Security Scan
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/anajuliabit-skill-security-scan/badge)](https://www.skillsdirectory.com/skills/anajuliabit-skill-security-scan)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: Skill Security Scan
description: Audit skills, workflows, and companion scripts for injection, exfiltration, traversal, and prompt-override risks with delta tracking, baseline suppression, issue filing, and per-finding remediation
var: ""
tags: [dev]
---
<!-- 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)
- `skills/*/*.sh` and `skills/*/*.py` (companion scripts that skills invoke)
- `.github/workflows/*.yml` (CI — especially `run:` 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.md` exists → 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`:
```yaml
# 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:
1. `skills/skill-security-scan/SKILL.md` — all prompt-override pattern matches inside the "Threat categories" section (documentation, not payload)
2. `skills/security-digest/SKILL.md` — any curl/token pattern inside fenced code blocks showing example usage

## Steps

1. **Read memory.** Read `memory/MEMORY.md` and today's `memory/logs/${today}.md` (create if missing) for context.

2. **Bootstrap baseline.** If `skills/security/scan-baseline.yml` does not exist, create it with the seed suppressions listed above and record `BASELINE_BOOTSTRAPPED` in the exit status.

3. **Resolve scope** per the `${var}` rules above. Log the chosen scope.

4. **Preflight scanner.** Verify `skills/skill-security-scan/scan.sh` is present and executable, then attempt to invoke it. **Two known failure modes trigger the inline Grep fallback:**
   - `missing` — file absent or non-executable (rare edge case).
   - `sandbox-blocked` — `scan.sh` execution denied at the sandbox approval gate. This is the **dominant** failure mode: every weekly run since 2026-05-25 has taken this path (7+ consecutive runs as of 2026-07-13). Treat sandbox denial as expected, not exceptional.

   In either case, fall back to inline Grep over the same HIGH/MEDIUM/LOW pattern library defined in `scan.sh`, plus the manual `${{ }}`→`run:` interpolation audit and obfuscation sweep — never silently skip. Record which trigger fired (`missing` or `sandbox-blocked`) on the log's `Scanner mode:` line so future reflect cycles can distinguish a real scanner regression from expected sandbox denial.

5. **Run scanner in JSON mode** — invoke `scan.sh --json` (or `--all --json` for the full corpus) and capture the structured output: `[{skill, status, file, high, medium, low}, ...]`. Do not parse stderr into findings. If invocation returns a sandbox denial rather than JSON, route back to step 4's `sandbox-blocked` fallback path — do not retry, do not surface the denial as a scan failure.

6. **Trusted-source filter.** Load `skills/security/trusted-sources.txt`. For each scanned file, check if the skill directory has an `origin:` 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 has `name`, `description`, `tags`, and a `var` key — emit no HIGH/MEDIUM/LOW findings for trusted sources, only format errors.

7. **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 a `run: |` / `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 actual `run:` steps in real workflow files — those execute.

8. **Apply baseline suppression.** Drop any finding whose (file, pattern, line) tuple is in `skills/security/scan-baseline.yml`.

9. **Compute delta** against `memory/state/security-scan.json` (previous run's finding set, keyed by `sha256(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)

10. **File/close issues** in `memory/issues/`:
    - For each NEW HIGH finding (post-suppression): create `memory/issues/ISS-{next_id}.md` with 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 to `INDEX.md` under `## Open`.
    - For each RESOLVED finding that corresponds to an open ISS filed by `skill-security-scan`: set `status: resolved`, `resolved_at: ${today}`, move the row from `## Open` to `## Resolved` in `INDEX.md`.
    - Do NOT file issues for NEW MEDIUM or LOW findings — those live in the article report only.

11. **Write the report** to `articles/security-scan-${today}.md` only if there are any NEW, RESOLVED, or current HIGH findings. Structure:

    ```markdown
    # 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.
    ```

12. **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 variable | Quote the variable; prefer `${VAR}` with explicit quoting; replace `eval` with a function |
    | `curl`/`wget` with an env var in the URL or body | Move secret into a pre-fetch script (see `CLAUDE.md` Sandbox section); never interpolate secrets into shell-block strings |
    | `${{ github.event.* }}` inside a `run:` block | Rebind the value to an `env:` key first, then read `$_SAFE_NAME` from the shell (see `articles/workflow-security-audit-2026-04-11.md`) |
    | Path-traversal sequence | Validate input against `skills/*/` or explicit allow-list; reject absolute paths |
    | Prompt-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_ROOT` or a specific subdir; never take a variable as the delete root |
    | Force-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 |

13. **Persist state.** Write the full current finding set to `memory/state/security-scan.json` so the next run can compute delta. Include `{generated_at, scope, findings: [{file, line, pattern, severity, fingerprint}]}`.

14. **Notify** via `./notify` only 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_OK` to stdout so heartbeat can log it.

15. **Log** to `memory/logs/${today}.md` with an `### skill-security-scan` section: 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 delta
- `SECURITY_SCAN_NEW` — at least one NEW HIGH finding
- `SECURITY_SCAN_RESOLVED` — no new HIGH findings, but at least one was resolved
- `SECURITY_SCAN_NOCHANGE` — findings exist but identical to last run
- `SECURITY_SCAN_BOOTSTRAPPED` — baseline file was just created; this run writes initial state
- `SECURITY_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. `scan.sh` execution is denied at the sandbox approval gate on every scheduled Actions run — that is expected, not a scanner outage. When denied (or if the file is missing), perform the scan inline using Grep with the same pattern library per step 4 — never silently skip. The `./notify` call is covered by the standard post-processor (see `CLAUDE.md` Sandbox section).

Files in this skill

  • SKILL.md12.4 KB
  • scan.sh9.6 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…