Imported from overspace-labs/HaESkill (
SKILL.md). Install upstream withnpx skills add overspace-labs/HaESkill. Copyright stays with the author.
HaE Skill
Rule-driven sensitive information scanning for local files. Rules come from rules/Rules.json (HaE rule set); matching is done by ripgrep and the artifact is a single JSON report. Nothing outside the Python standard library is needed — ripgrep on PATH is the only requirement.
When a request mentions curl, wget, downloading a URL, fetching a response,
HTTP response scanning, or checking a downloaded JavaScript/configuration file,
run scripts/hae_forward.py immediately. The first two arguments are exactly
curl|wget URL; any arguments after the URL are passed to that real client.
The script loads the shared rules/Rules.json, executes the real client once
with the complete argument list, captures its stdout in memory, scans that
stdout with one rg --json --pcre2 process per rule, and emits one aggregated
JSON report. It never issues a second request and never rewrites or filters the
client arguments.
Mandatory execution rule: Before executing any URL-fetching curl or
wget command, use scripts/hae_forward.py first. Do not execute the real
client directly for URL retrieval when this Skill is available. The wrapper
must receive the client name and URL first, with optional client arguments
after the URL. This applies even when the user asks only to "curl this URL",
"wget this file", download JavaScript, or show verbose request details:
python3 <skill-dir>/scripts/hae_forward.py curl URL [curl arguments...]
python3 <skill-dir>/scripts/hae_forward.py wget URL [wget arguments...]
python3 <skill-dir>/scripts/hae_forward.py curl https://example.com/app.js -sS -L
python3 <skill-dir>/scripts/hae_forward.py wget https://example.com/config.json --header='User-Agent: HaE'
The report has the same top-level shape as the file scanner: meta, summary,
and findings. Matches are attributed to their HaE group and rule. Only
stdout is scanned: if the original command writes the body to a file, returns
headers only, or otherwise produces no body on stdout, the report contains no
HTTP body findings. stderr remains the client's diagnostics. Runtime
dependencies are Python 3, the real curl or wget client, and rg. For local
files and saved responses, use scripts/hae_scan.py independently.
If the user explicitly requests shell-wide replacement, install wrappers that delegate to this script and ensure the wrapper directory precedes system paths:
mkdir -p "$HOME/.local/bin"
ln -sf "<skill-dir>/scripts/hae_forward.py" "$HOME/.local/bin/hae_forward.py"
cat > "$HOME/.local/bin/curl" <<'SH'
#!/bin/sh
exec python3 "$HOME/.local/bin/hae_forward.py" curl "$@"
SH
cat > "$HOME/.local/bin/wget" <<'SH'
#!/bin/sh
exec python3 "$HOME/.local/bin/hae_forward.py" wget "$@"
SH
chmod +x "$HOME/.local/bin/curl" "$HOME/.local/bin/wget" "$HOME/.local/bin/hae_forward.py"
export PATH="$HOME/.local/bin:$PATH"
Quick Start
A target is always required — the scanner never defaults to the current directory. Invoke the scanner through this Skill's own directory, since the working directory is usually some other project:
python3 <skill-dir>/scripts/hae_scan.py /abs/path/to/target
<skill-dir> is the directory this SKILL.md lives in. The command writes .hae/hae-scan-<timestamp>.json under the current directory and prints a per-rule summary to stdout. Read the JSON for details, not the whole file tree.
Core Principle
HaE's job is to collect matches completely and faithfully — nothing more, nothing less. The rule set carries no opinion about what matters: rule groups are labels, not priorities. What a match is worth is decided by the task at hand, during integration. Three obligations follow:
- Collect — read everything. The report is complete by design: the scanner has no truncation knobs; every file, every unique match value and every location is reported. Read the entire
findingsarray. Never pre-filter by group, never skip a group because of its name, never decide "this can't matter" before reading the matches. - Integrate — feed the task. HaE results are task input, not a side artifact to summarize and drop. Bring the collected matches into the task's working context as data. Nothing about a match's shape or rule group prescribes where it goes — that is decided later, per task.
- Associate — judge by the task. A match's relevance and severity follow from the current goal, not from which rule produced it. The same match (e.g. a URL carrying
?token=) can be the key finding in one task and irrelevant in another. Decide per task, with evidence.
The report is the single source of truth for what was collected: do not re-derive collected values with other tools, and do not draw conclusions before the whole report has been read.
While a scan runs, do not mine the same targets in parallel — extracting endpoints, tokens and URLs is the scan's job. Pre-empting it with ad-hoc grep/rg duplicates work and biases how the report is read afterward. Use scan time only for target-neutral preparation: probe design, script scaffolding, connectivity checks.
Workflow
Task Progress:
- [ ] Step 1: Confirm the explicit scan target
- [ ] Step 2: Run the scan
- [ ] Step 3: Collect — read the complete findings, every group, no pre-filtering
- [ ] Step 4: Verify matches against the source lines
- [ ] Step 5: Integrate & associate — fold findings into the task, judge by the task goal
- [ ] Step 6: Report grouped results with file:line references
Step 1: Confirm the target. Ask for a path if the user did not give one. Do not guess and do not scan /, $HOME or an entire disk. For a big tree, narrow with --ignore-ext or point at a subdirectory.
Step 2: Run the scan.
python3 <skill-dir>/scripts/hae_scan.py /path/to/app/dist /path/to/app/src
Add --group / --rule when the user only cares about one class of data:
python3 <skill-dir>/scripts/hae_scan.py /path/to/target --group "Sensitive Information" --group "Basic Information"
Step 3: Collect — read the complete report, every group, no pre-filtering. The stdout summary gives per-rule counts across all groups. Then dump the JSON findings array without dropping groups up front; a pre-filtered read is exactly how collected data gets lost. Use this query, which prints every group:
python3 -c "
import json,sys
d=json.load(open(sys.argv[1]))
for g in d['summary']['groups']:
print(f\"== {g['group']}: {g['unique']} unique / {g['total']} total ==\")
for f in d['findings']:
loc=f['locations'][0]
print(f\"{f['group']}/{f['rule']}\t{f['match']}\t{loc['file']}:{loc['line']}:{loc['column']}\tx{f['count']}\")
" .hae/hae-scan-<timestamp>.json
The scanner never truncates: every unique match value and every location is in the report. Read it all before judging anything. --group / --rule narrows a rerun; it does not replace reading the default full report.
Step 4: Verify before using. Matches are recall-oriented: they identify candidates, and verification is what separates a real value from lookalike text. Confirm each match you intend to use by reading the actual line:
sed -n '120,124p' /path/to/file
Judge per match, not per rule group. Common lookalikes to check against the source: placeholder values (example.com, 13800138000, xxx), test fixtures, vendored dependencies, minified library internals. Verified or not, every match stays in the report as collected data.
Step 5: Integrate & associate — fold findings into the task. Bring the collected matches into the working set, then judge each by the current task goal:
- Integrate: carry the matches into the working set as collected data. Where each match goes — probe list, test set, assumptions, or nowhere — follows from the task goal and the verified evidence, not from the rule group that produced it
- Judge by the task: relevance and severity come from the task goal — the same value may be the key finding in one task and irrelevant in another. State the reasoning per match
- Do not re-derive: values already in the report are collected; re-mining the same files with
grep/regex is duplicate work, and skipping parts of the report is lost data
Step 6: Report. Present the collected matches by rule group, always citing file:line, and state each match's relation to the task. See the reporting template below.
Options
| Option | Default | Purpose |
|---|---|---|
targets |
required | One or more files/directories |
--rules PATH |
<skill-dir>/rules/Rules.json |
Alternative rule set using the same reduced schema |
--group NAME |
all | Keep groups whose name contains NAME (repeatable) |
--rule NAME |
all | Keep rules whose name contains NAME (repeatable) |
-o, --out PATH |
.hae/hae-scan-<ts>.json |
Output path; - streams JSON to stdout |
--context N |
50 |
Context characters kept around each match, 0 disables |
--max-count N |
0 |
ripgrep --max-count per file, 0 = unlimited |
--ignore-ext EXT |
image extensions | Extension to skip (repeatable, replaces defaults) |
--timeout SEC |
180 |
Per-pattern ripgrep timeout, 0 = none |
--jobs N |
CPU cores, max 32 | Parallel scan workers; each runs one ripgrep and parses its JSON output in its own process |
--list-rules |
off | Print the loaded rules as JSON and exit |
Report Shape
{
"meta": {
"targets": ["/abs/path"], "rules_file": "...", "rules_scanned": 41,
"patterns_executed": 41, "duration_ms": 94,
"ripgrep": { "path": "/opt/homebrew/bin/rg", "version": "ripgrep 15.1.0" },
"errors": [{ "rules": ["Group/Rule"], "message": "..." }]
},
"summary": {
"total_matches": 9216, "unique_matches": 284, "files_with_matches": 29,
"groups": [{ "group": "Other", "total": 9136, "unique": 242,
"rules": [{ "rule": "Request URI", "total": 9055, "unique": 200, "files": 29 }] }]
},
"findings": [
{ "group": "Basic Information", "rule": "Email", "match": "zhangsan@example.com",
"count": 3,
"locations": [{ "file": "/abs/f.js", "line": 1, "column": 17,
"context": { "before": "// contact ", "after": " for support" } }] }
]
}
Findings are deduplicated per (group, rule, match); count is the number of distinct locations and locations lists every one of them. line and column are 1-based.
Always check meta.errors: an error means that rule produced nothing, so the target deserves a rescan. The scanner has no truncation knobs — every file, every unique match value and every location is always reported; trimming is left to the consumer.
Reporting Template
## HaE Scan Results
**Target**: /path/to/target
**Scope**: 41 rules, <N> matches (<U> unique values), <F> files with hits
### Collected (by group, complete — nothing dropped)
- **<Group>/<Rule>** — <N> hits, example `<match>` @ [file:line](file:///abs/path#L12)
- …
### Relation to the Task
- Which matches are carried into the working set and how they will be used, per the task goal
- Per-match reasoning: why each used match matters for the current goal, or why it was set aside
- Which matches were verified against the source, and what the source line showed
**Report file**: .hae/hae-scan-<timestamp>.json
Rule Groups
| Group | Content |
|---|---|
| Fingerprint | Shiro, JWT, Swagger UI, Druid, Vite DevMode, … |
| Maybe Vulnerability | Java deserialization, debug parameters, upload forms, … |
| Basic Information | Email, phone, ID card, IP, domain |
| Sensitive Information | Keys, tokens, credential fields, cloud AK/SK, auth fields |
| Other | Linkfinder, All URL, Source Map, URL Schemes, … |
The table is factual only — no group carries a priority, and none may be skipped because of its name. Some rules produce many matches on ordinary source code (for example Linkfinder and All URL in this rule set); that is a property of the rule, not a judgment about the matches. Read every group's matches in Step 3; decide what matters in Step 5.
Rules Maintenance
rules/Rules.json is a copy of the HaE rule set reduced to what file scanning needs:
{
"rules": [
{
"group": "Fingerprint",
"rule": [
{ "name": "Shiro", "regex": "(=deleteMe|rememberMe=)", "sensitive": true }
]
}
]
}
Only group, name, regex, sensitive are used. HaE's HTTP-oriented fields (scope, engine, color, validator) and the two-stage s_regex / format extraction are intentionally dropped — this Skill has no HTTP message to scope, no UI to color and no request context to validate against.
To add or refresh rules, edit this file directly: copy the rule from a HaE rule set, rename f_regex to regex and drop the other fields. HaE ships its rules as YAML, and --rules still accepts a .yml file when PyYAML happens to be installed. The field mapping is in reference.md.
Additional Resources
For rule field semantics, the ripgrep invocation, capture-group extraction, the HaE field mapping and troubleshooting, see reference.md.