Skip to content

Commit 50ff3e0

Browse files
authored
Crawler hardening pass (#20)
* chore: commit wip changes on feature/wip-20260705 * Crawler hardening pass - Update AGENTS.md to the shared hawk-eco extension-authoring format
1 parent 69d0529 commit 50ff3e0

7 files changed

Lines changed: 173 additions & 1495 deletions

File tree

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Canonical CI workflow for hawk-eco Go repos.
2-
# Source of truth: .shared-templates/workflows/go-ci.yml.tmpl
2+
# Source of truth: https://github.com/GrayCodeAI/hawk/blob/main/.shared-templates/workflows/go-ci.yml.tmpl
33
#
44
# Two deployment models:
55
#

AGENTS.md

Lines changed: 160 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -1,109 +1,168 @@
1-
# AGENTS.md — Inspect
1+
---
2+
description: Extending hawk-eco — how to write AGENTS.md files, custom specialists, skills, hooks, MCP servers, and plugins.
3+
globs: "*.go, *.js, *.md, *.json, *.toml, *.yaml, *.yml"
4+
alwaysApply: false
5+
---
26

3-
Website security auditing and crawling library for Go. Crawls sites concurrently, runs checks and declarative rules, generates findings with severity and CWE references.
7+
# Extending hawk-eco
48

5-
## Design Principles
9+
hawk-eco is an open-source code intelligence platform. This document describes how to extend it with custom tools, skills, hooks, and integrations.
610

7-
- **Library** — importable Go library + embeddable MCP server (no CLI binary)
8-
- **No LLM dependency** — pure static analysis on crawled pages
9-
- **Extensible** — custom checks (Go code) + declarative rules (no code required)
11+
## 1. Drop a project `AGENTS.md`
1012

11-
## Build & Test
13+
When hawk-eco starts in a directory, it looks for project-level instructions and injects them into the system prompt. The lookup walks from your current working directory **up to the nearest git root** and reads the first matching file at each level — general rules at the repo root, more specific rules in sub-trees. Files are labeled with their directory in the prompt (e.g. `## Project guidelines (services/api/AGENTS.md)`).
14+
15+
Accepted file names, in priority order at each level:
16+
17+
| Path | Notes |
18+
| --- | --- |
19+
| `./AGENTS.md` | The classic spot — committed to your repo, shared with the team. |
20+
| `./ZERO.md` | Brand-specific alias. Same format, lower priority. |
21+
| `./.zero/AGENTS.md` | Project-local, hidden, gitignored. Personal notes that stay out of git. |
22+
23+
Matching is **case-insensitive** on the basename, so `AGENTS.md`, `Agents.md`, and `agents.md` resolve to the same file on Windows and macOS. The git-tracked filename in this repo is `AGENTS.md` — keep that on case-sensitive filesystems (Linux, the WSL filesystem, or a CI runner) to match what the loader looks for.
24+
25+
Both files use the same format. YAML frontmatter is optional; the markdown body is loaded as instructions for the agent. hawk-eco reads the file once at session start, so changes take effect on the next launch — not mid-session.
26+
27+
```markdown
28+
# Project conventions for <your project>
29+
30+
- Build with `make`, not `go build` directly.
31+
- Tests live next to the source file (`foo_test.go` next to `foo.go`).
32+
- Run `make lint` before opening a PR.
33+
- Never edit files under `third_party/` — those are vendored.
34+
```
35+
36+
Tips:
37+
38+
- Keep each file under ~8 KiB. hawk-eco caps the **total** across all matched files at 32 KiB; everything past the cap is dropped.
39+
- Re-state rules in the imperative voice: "Run `make lint`", not "you should consider running the linter".
40+
- Don't put secrets, model IDs, or environment-specific paths in `AGENTS.md`. Use config files for those.
41+
- In a monorepo, drop a narrower `AGENTS.md` in each sub-tree (e.g. `services/api/AGENTS.md`). hawk-eco picks those up automatically when you launch from inside the sub-tree.
42+
- A YAML frontmatter block (`---\n...\n---`) at the top is preserved verbatim in the injected prompt but is not parsed for `globs:` or `alwaysApply:` scoping today — keep the body self-contained.
43+
44+
### Personal guidelines, across every project
45+
46+
For preferences that follow *you*, not a specific repo (tone, tooling habits, workflow), drop a `ZERO.md` in your user config directory: `~/.config/hawk-eco/ZERO.md` on Linux/macOS, `%AppData%\Roaming\hawk-eco\ZERO.md` on Windows — the same directory as config files and your personal specialists. Same format and 8 KiB cap as the project files above, and the same case-insensitive basename match.
47+
48+
This file is injected as its own `## User guidelines` section, before the project's `AGENTS.md`/`ZERO.md`, and is labeled as personal preference in the prompt: project guidelines are the later, more specific instruction and take precedence over it when the two conflict.
49+
50+
## 2. Custom specialists
51+
52+
Specialists are hawk-eco's sub-agents. Three scopes, in priority order:
53+
54+
| Scope | Path | Shared? |
55+
| --- | --- | --- |
56+
| Built-in | compiled into hawk-eco | yes |
57+
| User | `~/.config/hawk-eco/specialists/*.md` | no — your machine only |
58+
| Project | `./.zero/specialists/*.md` | yes — the repo team |
59+
60+
Project overrides user overrides built-in when names collide.
61+
62+
A specialist is a markdown manifest with frontmatter and a system prompt:
63+
64+
```markdown
65+
---
66+
description: Reviews API changes for breaking-change risk and missing tests.
67+
tools: read-only,plan
68+
---
69+
70+
You review API changes. For every changed hunk in `internal/api/` or any file
71+
that ends in `_api.go`:
72+
73+
1. Confirm the public signature is backward-compatible, or note the breaking
74+
change explicitly with the migration path.
75+
2. Confirm a corresponding test exists in `internal/api/*_test.go` and that
76+
the new behaviour is exercised.
77+
3. Flag any new exported symbol without a doc comment.
78+
79+
Reply with one JSON object per finding: `{"file", "line", "severity", "message", "fix"}`.
80+
```
81+
82+
CLI management:
1283

1384
```bash
14-
go test ./... # Run all tests
15-
go test -race ./... # Race detector
16-
go test -coverprofile=c.out ./... # Coverage
17-
go vet ./... # Static analysis
18-
gofumpt -w . # Format
85+
hawk-eco specialist list
86+
hawk-eco specialist show api-reviewer
87+
hawk-eco specialist create api-reviewer \
88+
--project \
89+
--description "Reviews API changes" \
90+
--tools read-only,plan \
91+
--prompt "$(cat api-reviewer.md)"
92+
hawk-eco specialist edit api-reviewer --project
93+
hawk-eco specialist delete api-reviewer --project
94+
hawk-eco specialist path # prints the resolved specialists directory
1995
```
2096

21-
## Architecture
22-
23-
- `crawler.go` — Concurrent website crawler with depth control
24-
- `check.go` — Check interface and built-in security checks
25-
- `rule.go` — Declarative rule engine (YAML-based)
26-
- `finding.go` — Findings with severity, CWE, and evidence
27-
- `report.go` — Report generation (JSON, SARIF, HTML)
28-
29-
## Conventions
30-
31-
- Go 1.26+, pure Go, no CGO
32-
- Table-driven tests
33-
- Conventional Commits: `feat:`, `fix:`, `docs:`, `refactor:`, `test:`
34-
- No `Co-authored-by:` trailers (auto-stripped by githook)
35-
- `gofumpt` formatting enforced in CI
36-
- CWE references required for all security findings
37-
38-
## Common Pitfalls
39-
40-
- Crawler tests need HTTP test servers — use `httptest.NewServer`
41-
- Rule YAML must be validated before execution
42-
- Session cookie matching uses substring, not exact match
43-
44-
## Naming Conventions
45-
46-
- **Types are domain nouns**: `Finding`, `Report`, `Stats`, `Page`, `PageLink`, `Checker`, `RuleCheck`
47-
- **Option functions use `With` prefix**: `WithChecks()`, `WithDepth()`, `WithConcurrency()`, `WithAllowPrivateIPs()`
48-
- **Preset options are bare vars**: `Quick`, `Standard`, `Deep`, `SecurityOnly`, `CI` — exported `var Option` values
49-
- **Severity is a type alias**: `type Severity = types.Severity` from `hawk-core-contracts/types` — shared across hawk-eco
50-
- **Internal adapters use `Adapter` suffix**: `ruleCheckAdapter`, `customCheckAdapter` — bridge public to internal interfaces
51-
- **Check names are lowercase strings**: `"security"`, `"links"`, `"forms"`, `"a11y"`, `"performance"` — used in `WithChecks()`
52-
- **Error handling**: `Scan()` returns `(*Report, error)` — validation errors for empty URL, nil errors for success
53-
54-
## API Patterns
55-
56-
- **Functional options pattern**: same as sight — `Option` interface with `optFunc` adapter, `buildConfig()` merge
57-
- **One-shot + reusable**: `Scan(ctx, target, opts...)` creates a `Scanner` internally; `NewScanner(opts...)` for reuse
58-
- **Checker interface for extensibility**: `Name() string` + `Run(ctx, pages) []Finding` — register via `RegisterCheck()`
59-
- **RuleCheck for declarative rules**: `HeaderMatch`, `HeaderMissing`, `BodyMatch`, `BodyMissing`, `URLMatch` patterns
60-
- **Global + per-scanner custom checks**: `RegisterCheck()`/`RegisterRule()` for global; pass slices to `Scanner` for scoped
61-
- **Report.Failed()**: checks if any finding meets `FailOn` severity threshold — same pattern as sight
62-
- **ReDoS protection**: all user-supplied regex patterns go through `compileWithTimeout()` and `matchWithTimeout()` with 1s/100ms limits
63-
- **Regex complexity check**: `checkRegexComplexity()` rejects nested quantifiers and deep group nesting before compilation
64-
65-
## Testing Patterns
66-
67-
- **External test package**: `package inspect_test` — tests import `inspect` as a consumer would
68-
- **httptest.NewServer for all tests**: each test spins up a mock HTTP server with specific HTML/headers/responses
69-
- **Test patterns by concern**: `TestScan_BasicSite` (links), `TestScan_SecurityHeaders`, `TestScan_FormCSRF`, `TestScan_Accessibility`
70-
- **Always pass `WithAllowPrivateIPs()`**: tests run against `127.0.0.1` — without this flag, localhost is blocked
71-
- **Always pass `WithDepth(1)`**: keeps tests fast by limiting crawl depth
72-
- **Finding assertions**: iterate `report.Findings` and check specific `Check`, `Severity`, `Message` fields
73-
- **Preset smoke test**: `TestScan_Presets` runs all presets against a simple server — catches config panics
74-
- **ClearCustomChecks() in tests**: call before registering test-specific checks to avoid global state leaks
75-
- **Report method tests**: `TestReport_Failed`, `TestReport_MaxSeverity` — test on struct literals, no HTTP needed
76-
77-
## Refactoring Guidelines
78-
79-
- **Safe to refactor**: `checkRegexComplexity()`, `compileWithTimeout()`, `matchWithTimeout()` — internal helpers
80-
- **Safe to refactor**: `truncateEvidence()`, `intIn()` — pure utility functions
81-
- **Safe to refactor**: `parseInspectTOML()`, `parseInspectKeyValue()`, `applyFileConfig()` — config parsing internals
82-
- **Do not touch**: `Checker` interface (`Name()`, `Run()`) — breaking change for all custom check implementations
83-
- **Do not touch**: `RuleCheck` struct field names — used by consumers to define declarative rules
84-
- **Do not touch**: `Finding`, `Report`, `Stats` struct field names/tags — JSON serialization contract
85-
- **Safe to extend**: add new `Option` functions, new presets, new built-in checks in `checks/` package
86-
- **When adding checks**: create a new file in `checks/`, implement `Checker` interface, register in `init()`
87-
88-
## Key File Locations
89-
90-
| What | Where |
91-
|---|---|
92-
| Public API entry point | `inspect.go` (types, `Scan()`, `Finding`, `Report`, `Stats`) |
93-
| Check interface & adapters | `check.go` (`Checker`, `RuleCheck`, `RegisterCheck()`, `RegisterRule()`, ReDoS protection) |
94-
| Scanner implementation | `scanner.go` (crawler orchestration, check execution) |
95-
| Configuration & presets | `options.go` (`config` struct, `With*` functions, presets) |
96-
| Config file loading | `config.go` (`.inspect.toml` parsing, `LoadConfig()`) |
97-
| Severity type alias | `severity.go` (re-exports from `hawk-core-contracts/types`) |
98-
| SARIF output | `sarif.go` |
99-
| CI output formatting | `ci_output.go` |
100-
| Built-in checks | `checks/` directory |
101-
| Internal crawler | `internal/crawler/` |
102-
| Internal check runner | `internal/check/` |
103-
| Browser-based crawling | `browser.go`, `browser/` |
104-
| LLM scanner integration | `llm_scanner.go` |
105-
| API security checks | `api_security.go` |
106-
| Dependency checking | `dependency_check.go` |
107-
| SBOM generation | `sbom.go` |
108-
| Main test file | `inspect_test.go` (httptest servers, per-concern scenarios) |
109-
| Linter config | `.golangci.yml` (errcheck, govet, staticcheck, gocritic, bodyclose, noctx) |
97+
## 3. Skills
98+
99+
Skills are markdown instruction files that extend agent capabilities. They can be:
100+
- Project-scoped: dropped in `./.zero/skills/` or `./skills/`
101+
- User-scoped: dropped in `~/.config/hawk-eco/skills/`
102+
103+
A skill manifest:
104+
105+
```markdown
106+
---
107+
description: How to review Go code for security issues
108+
globs: "*.go"
109+
alwaysApply: true
110+
---
111+
112+
When reviewing Go code for security:
113+
114+
1. Check for SQL injection patterns
115+
2. Verify error handling doesn't expose sensitive data
116+
3. Confirm secrets are not hardcoded
117+
4. Validate input sanitization
118+
```
119+
120+
## 4. Hooks
121+
122+
Hooks allow custom commands to run at specific lifecycle points:
123+
- `beforeReview` — runs before code review starts
124+
- `afterReview` — runs after code review completes
125+
- `sessionStart` — runs at session initialization
126+
- `sessionEnd` — runs at session teardown
127+
128+
```bash
129+
hawk-eco hook add beforeReview --command "lint-check"
130+
hawk-eco hook remove beforeReview
131+
hawk-eco hook list
132+
```
133+
134+
## 5. MCP integration
135+
136+
MCP (Model Context Protocol) servers can expose tools to hawk-eco:
137+
138+
```bash
139+
hawk-eco mcp add --name server --url http://localhost:8080
140+
hawk-eco mcp remove server
141+
hawk-eco mcp list
142+
```
143+
144+
## 6. Plugins
145+
146+
Plugins extend hawk-eco with custom tools and capabilities:
147+
148+
```bash
149+
hawk-eco plugin add --name my-plugin --path ./my-plugin
150+
hawk-eco plugin remove my-plugin
151+
hawk-eco plugin list
152+
```
153+
154+
## 7. Verification
155+
156+
hawk-eco includes a self-verification system to validate local changes before contributing:
157+
158+
```bash
159+
hawk-eco verify
160+
hawk-eco verify --fix
161+
```
162+
163+
## Development
164+
165+
```bash
166+
make lint
167+
hawk-eco verify
168+
```

Makefile

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Canonical hawk-eco Makefile for Go library repos.
2-
# Source of truth: .shared-templates/Makefile.library.tmpl at the eco root.
2+
# Source of truth: https://github.com/GrayCodeAI/hawk/blob/main/.shared-templates/Makefile.library.tmpl
33
# Placeholders rendered per repo: inspect.
44

55
# ---------------------------------------------------------------------------
@@ -115,5 +115,7 @@ help: ## Show this help.
115115
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-15s\033[0m %s\n", $$1, $$2}'
116116

117117
.PHONY: hooks
118-
hooks:
119-
git config core.hooksPath .githooks
118+
hooks: ## Install git hooks via lefthook (format, lint, conventional commits, co-author strip).
119+
@command -v lefthook >/dev/null 2>&1 || (echo "install: go install github.com/evilmartians/lefthook@latest" && exit 1)
120+
git config --unset core.hooksPath 2>/dev/null || true
121+
lefthook install

internal/crawler/crawler.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,8 +565,11 @@ func isPrivateIP(ip net.IP) bool {
565565
{mustParseCIDR("172.16.0.0/12")},
566566
{mustParseCIDR("192.168.0.0/16")},
567567
{mustParseCIDR("127.0.0.0/8")},
568+
{mustParseCIDR("169.254.0.0/16")}, // link-local, incl. cloud metadata (169.254.169.254)
569+
{mustParseCIDR("100.64.0.0/10")}, // CGNAT shared address space
568570
{mustParseCIDR("::1/128")},
569571
{mustParseCIDR("fc00::/7")},
572+
{mustParseCIDR("fe80::/10")}, // IPv6 link-local
570573
}
571574
for _, r := range privateRanges {
572575
if r.network.Contains(ip) {

lefthook.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Canonical lefthook config for hawk-eco Go repos.
2-
# Source of truth: .shared-templates/lefthook.yml.tmpl
2+
# Source of truth: https://github.com/GrayCodeAI/hawk/blob/main/.shared-templates/lefthook.yml.tmpl
33
#
44
# Install lefthook:
55
# brew install lefthook (macOS)
@@ -75,6 +75,9 @@ pre-commit:
7575
pre-push:
7676
commands:
7777

78+
boundaries:
79+
run: bash ./scripts/check-ecosystem-boundaries.sh
80+
7881
test:
7982
run: go test ./... -count=1 -timeout=60s
8083

0 commit comments

Comments
 (0)