inspect

package module
v0.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 11, 2026 License: MIT Imports: 14 Imported by: 0

README

inspect

Website security auditing and crawling library for Go. Crawls sites concurrently, runs checks and declarative rules, generates findings with severity and CWE references.

Design

  • Library + CLI — importable library with optional inspect-ci binary
  • No LLM dependency — pure static analysis on crawled pages
  • Extensible — custom checks (Go code) + declarative rules (no code required)

Install

go get github.com/GrayCodeAI/inspect@latest

Usage

One-shot scan
report, err := inspect.Scan(ctx, "https://example.com", inspect.Standard)
for _, f := range report.Findings {
    fmt.Printf("[%s] %s: %s\n", f.Severity, f.URL, f.Message)
}
if report.Failed() {
    os.Exit(1)
}
Reusable scanner
scanner := inspect.NewScanner(inspect.Standard)
r1, _ := scanner.Scan(ctx, "https://site-a.com")
r2, _ := scanner.Scan(ctx, "https://site-b.com")
Directory scan (local files)
report, _ := scanner.ScanDir(ctx, "./public")

Presets

Preset Depth Checks Use case
Quick 1 security Fast header check
Standard 3 all (default) Balanced audit
Deep 10 all Comprehensive crawl
SecurityOnly 3 security Security audit
CI 3 all + fail-on CI/CD gates

Built-in Checks

Check Detects
Security Headers Missing CSP, HSTS, X-Frame-Options, X-Content-Type-Options
TLS/Certificates Weak ciphers, expired certs, mixed content
CORS Overly permissive Access-Control-Allow-Origin
Broken Links 404s, timeouts, unreachable URLs
Forms Missing CSRF tokens, insecure action URLs
Accessibility Missing alt text, contrast issues, ARIA violations
Performance Large resources, render-blocking scripts
SEO Missing meta tags, broken structured data
SRI Missing subresource integrity on CDN scripts

Custom Checks

type Checker interface {
    Name() string
    Run(ctx context.Context, pages []*Page) []Finding
}

inspect.RegisterCheck(&MyCustomChecker{})

Declarative Rules

inspect.RegisterRule(inspect.RuleCheck{
    RuleName:      "HSTS Missing",
    RuleSeverity:  inspect.High,
    HeaderMissing: []string{"Strict-Transport-Security"},
    FixSuggestion: "Add HSTS header with max-age >= 31536000",
})

Configuration

File-based config via .inspect.toml:

depth = 5
concurrency = 10
timeout = "30s"
fail-on = "high"
checks = ["security", "links", "forms"]
exclude = ["/admin/*", "/api/*"]

Output Formats

  • Terminal (colored, human-readable)
  • JSON
  • SARIF (static analysis interchange)
  • JUnit XML (CI integration)
  • HTML report
  • Markdown

CI/CD Integration

GitHub Action
- uses: GrayCodeAI/inspect@v0.4.0
  with:
    url: https://staging.example.com
    fail-on: high
    format: sarif
CLI
inspect-ci --url https://example.com --fail-on high --format junit

Testing

make test        # Unit tests
make test-race   # With race detector
make bench       # Benchmarks
make cover       # Coverage report

License

MIT

Documentation

Overview

Package inspect crawls websites and detects broken links, security issues, form problems, accessibility violations, and performance concerns.

It is designed as a standalone library imported by hawk — it has no CLI, no LLM dependency, and no TUI. Hawk wires inspect into its own commands.

Usage:

report, err := inspect.Scan(ctx, "https://example.com", inspect.Standard)
for _, f := range report.Findings {
    fmt.Printf("[%s] %s: %s\n", f.Severity, f.URL, f.Message)
}

For high-throughput or repeated scans, use the reusable Scanner:

scanner := inspect.NewScanner(inspect.Standard)
r1, _ := scanner.Scan(ctx, "https://site-a.com")
r2, _ := scanner.Scan(ctx, "https://site-b.com")

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ClearCustomChecks

func ClearCustomChecks()

ClearCustomChecks removes all registered custom checks and rules. Useful in tests.

func RegisterCheck

func RegisterCheck(c Checker)

RegisterCheck registers a custom check that will run alongside built-in checks. Call this before Scan() to include custom logic.

func RegisterRule

func RegisterRule(rule RuleCheck)

RegisterRule registers a declarative rule-based check. Rules are simpler than full Checker implementations — just pattern matching.

Types

type AXNode added in v0.2.0

type AXNode struct {
	Role        string
	Name        string
	Description string
	Value       string
	Properties  map[string]string
	Children    []AXNode
	Ignored     bool
}

AXNode represents a node in the computed accessibility tree.

type AxeNode added in v0.2.0

type AxeNode struct {
	HTML           string
	Target         []string // CSS selectors
	FailureSummary string
}

AxeNode represents a specific DOM element that violates a rule.

type AxeViolation added in v0.2.0

type AxeViolation struct {
	ID          string // rule ID (e.g., "color-contrast")
	Impact      string // "critical", "serious", "moderate", "minor"
	Description string
	Help        string
	HelpURL     string
	Nodes       []AxeNode
}

AxeViolation represents an axe-core accessibility violation.

type BrowserEngine added in v0.2.0

type BrowserEngine interface {
	RenderPage(ctx context.Context, url string, opts BrowserOpts) (*PageData, error)
	Close() error
}

BrowserEngine is the interface for optional browser-based page analysis. The core inspect package never imports rod — consumers provide an implementation via the inspect/browser sub-module.

type BrowserOpts added in v0.2.0

type BrowserOpts struct {
	Viewport   Viewport
	WaitFor    string // CSS selector to wait for before analysis
	Timeout    time.Duration
	InjectAxe  bool // inject axe-core and return accessibility violations
	Screenshot bool // capture full-page screenshot
	UserAgent  string
}

BrowserOpts configures a single browser page render.

type Checker

type Checker interface {
	Name() string
	Run(ctx context.Context, pages []*Page) []Finding
}

Checker is the public interface for custom checks. Implement this to add your own audit logic and register it with RegisterCheck.

type FileConfig

type FileConfig struct {
	Depth               int      `json:"depth"`
	Checks              []string `json:"checks"`
	Exclude             []string `json:"exclude"`
	FailOn              string   `json:"fail_on"`
	Concurrency         int      `json:"concurrency"`
	RateLimit           int      `json:"rate_limit"`
	Timeout             string   `json:"timeout"`
	PageTimeout         string   `json:"page_timeout"`
	UserAgent           string   `json:"user_agent"`
	AuthHeader          string   `json:"auth_header"`
	AuthValue           string   `json:"auth_value"`
	AcceptedStatusCodes []int    `json:"accepted_status_codes"`
}

FileConfig represents the contents of an .inspect.toml configuration file.

type Finding

type Finding struct {
	Check    string   `json:"check"`
	Severity Severity `json:"severity"`
	URL      string   `json:"url"`
	Element  string   `json:"element,omitempty"`
	Message  string   `json:"message"`
	Fix      string   `json:"fix,omitempty"`
	Evidence string   `json:"evidence,omitempty"`
}

Finding represents a single issue detected during a scan.

type NetworkEntry added in v0.2.0

type NetworkEntry struct {
	URL        string
	Method     string
	Status     int
	MimeType   string
	Size       int64
	Duration   time.Duration
	Failed     bool
	FailReason string
}

NetworkEntry represents a network request made during page load.

type Option

type Option interface {
	// contains filtered or unexported methods
}

Option configures a scan operation.

var CI Option = optFunc(func(c *config) {
	c.depth = 5
	c.checks = []string{"links", "security", "forms", "a11y", "perf", "seo"}
	c.concurrency = 10
	c.failOn = SeverityHigh
})

CI configures for continuous integration: standard checks, fail on high, JSON output.

var Deep Option = optFunc(func(c *config) {
	c.depth = 0
	c.checks = []string{"links", "security", "forms", "a11y", "perf", "seo"}
	c.concurrency = 20
})

Deep performs an exhaustive crawl with no depth limit.

var Quick Option = optFunc(func(c *config) {
	c.depth = 2
	c.checks = []string{"links"}
	c.concurrency = 5
})

Quick performs a shallow crawl checking only broken links.

var SecurityOnly Option = optFunc(func(c *config) {
	c.checks = []string{"security"}
})

Security limits the scan to security-related checks.

var Standard Option = optFunc(func(c *config) {
	c.depth = 5
	c.checks = []string{"links", "security", "forms", "a11y", "perf", "seo"}
	c.concurrency = 10
})

Standard performs a balanced crawl with all checks enabled.

func LoadConfig

func LoadConfig(dir string) ([]Option, error)

LoadConfig reads .inspect.toml or .inspect.yaml from the given directory (searching upward to parent directories). Returns nil options and nil error if no config file is found. Returns an error only on malformed files.

func WithAcceptedStatusCodes

func WithAcceptedStatusCodes(codes ...int) Option

WithAcceptedStatusCodes sets the HTTP status codes considered acceptable by the link checker. By default (when no codes are specified), status codes 200-399 are accepted.

func WithAuth

func WithAuth(header, value string) Option

func WithBlockPrivateIPs added in v0.5.0

func WithBlockPrivateIPs() Option

WithBlockPrivateIPs enables SSRF protection that blocks requests to private IP ranges. By default, private IPs are allowed since inspect is typically used to scan your own servers.

func WithBrowser added in v0.2.0

func WithBrowser(engine BrowserEngine) Option

WithBrowser sets a BrowserEngine for browser-rendered page analysis. When set, the scanner uses the engine to render pages with full JavaScript execution instead of relying solely on raw HTTP fetches for HTML analysis. The browser sub-module (github.com/GrayCodeAI/inspect/browser) provides a rod-based implementation.

func WithChecks

func WithChecks(checks ...string) Option

func WithConcurrency

func WithConcurrency(n int) Option

func WithCookieJar

func WithCookieJar(jar http.CookieJar) Option

func WithDepth

func WithDepth(n int) Option

func WithExclude

func WithExclude(patterns ...string) Option

func WithFailOn

func WithFailOn(sev Severity) Option

func WithFollowRedirects

func WithFollowRedirects(max int) Option

func WithLogger

func WithLogger(l *slog.Logger) Option

func WithPageTimeout

func WithPageTimeout(d time.Duration) Option

func WithRateLimit

func WithRateLimit(reqPerSec int) Option

func WithRespectRobots

func WithRespectRobots(enabled bool) Option

func WithTimeout

func WithTimeout(d time.Duration) Option

func WithUserAgent

func WithUserAgent(ua string) Option

type Page

type Page struct {
	URL        string
	StatusCode int
	Headers    map[string]string
	Body       []byte
	Links      []PageLink
	Depth      int
	ParentURL  string
}

Page is the public representation of a crawled page, exposed to custom checks.

type PageData added in v0.2.0

type PageData struct {
	FinalURL      string
	Title         string
	RenderedHTML  string
	AccessTree    []AXNode
	AxeViolations []AxeViolation
	ConsoleErrors []string
	NetworkLog    []NetworkEntry
	Screenshot    []byte
	LoadTime      time.Duration
}

PageData holds results from browser-rendered page analysis.

type PageLink struct {
	Href     string
	Text     string
	External bool
}

PageLink represents a link found on a page.

type Report

type Report struct {
	Target      string        `json:"target"`
	Findings    []Finding     `json:"findings"`
	Stats       Stats         `json:"stats"`
	CrawledURLs int           `json:"crawled_urls"`
	Duration    time.Duration `json:"duration"`
	FailOn      Severity      `json:"fail_on"`
}

Report is the complete result of a scan operation.

func Scan

func Scan(ctx context.Context, target string, opts ...Option) (*Report, error)

Scan crawls the target URL and runs all configured checks. This is the primary entry point for one-off scans.

func (*Report) Failed

func (r *Report) Failed() bool

Failed returns true if any finding meets or exceeds the configured fail threshold.

func (*Report) MaxSeverity

func (r *Report) MaxSeverity() Severity

MaxSeverity returns the highest severity found in the report.

type RuleCheck

type RuleCheck struct {
	RuleName     string
	RuleSeverity Severity
	Description  string
	// Match conditions (any match triggers a finding)
	HeaderMatch   map[string]string // header name → regex pattern (match = issue)
	HeaderMissing []string          // headers that MUST be present
	BodyMatch     []string          // regex patterns in body (match = issue)
	BodyMissing   []string          // regex patterns that MUST be present
	URLMatch      string            // only apply to URLs matching this regex
	StatusCodes   []int             // only apply to these status codes (empty = all)
	FixSuggestion string
}

RuleCheck defines a declarative check via patterns — no Go code required. This is the equivalent of Nuclei templates but for site auditing.

type Scanner

type Scanner struct {
	// contains filtered or unexported fields
}

Scanner is a reusable site auditor. Create one with NewScanner and call Scan multiple times. It is safe for concurrent use.

func NewScanner

func NewScanner(opts ...Option) *Scanner

NewScanner creates a configured Scanner. Apply presets and options:

s := inspect.NewScanner(inspect.Standard, inspect.WithDepth(3))

func (*Scanner) Scan

func (s *Scanner) Scan(ctx context.Context, target string) (*Report, error)

Scan crawls the target URL and runs all configured checks against the discovered pages. Returns a complete Report with findings and stats.

func (*Scanner) ScanDir

func (s *Scanner) ScanDir(ctx context.Context, dir string) (*Report, error)

ScanDir scans a local directory by starting a temporary file server. Useful for auditing build output before deployment.

type Severity

type Severity int

Severity represents the impact level of a finding.

const (
	SeverityInfo Severity = iota
	SeverityLow
	SeverityMedium
	SeverityHigh
	SeverityCritical
)

func ParseSeverity

func ParseSeverity(s string) Severity

ParseSeverity converts a string to a Severity.

func (Severity) AtLeast

func (s Severity) AtLeast(threshold Severity) bool

AtLeast returns true if s >= threshold.

func (Severity) String

func (s Severity) String() string

type Stats

type Stats struct {
	PagesScanned     int                      `json:"pages_scanned"`
	FindingsTotal    int                      `json:"findings_total"`
	BySeverity       map[Severity]int         `json:"by_severity"`
	ByCheck          map[string]int           `json:"by_check"`
	DurationPerCheck map[string]time.Duration `json:"duration_per_check"`
}

Stats provides scan metrics, broken down by severity and check type.

type Viewport added in v0.2.0

type Viewport struct {
	Width  int
	Height int
	Mobile bool
}

Viewport specifies the browser viewport dimensions.

Directories

Path Synopsis
cmd
inspect-ci command
Command inspect-ci is a lightweight CLI entrypoint for CI/CD pipelines and the GitHub Action.
Command inspect-ci is a lightweight CLI entrypoint for CI/CD pipelines and the GitHub Action.
internal
check
Package check implements the check registry and individual site audit checks.
Package check implements the check registry and individual site audit checks.
crawler
Package crawler implements a concurrent website crawler with rate limiting, depth control, URL deduplication, and robots.txt compliance.
Package crawler implements a concurrent website crawler with rate limiting, depth control, URL deduplication, and robots.txt compliance.
report
Package report provides formatting utilities for scan results.
Package report provides formatting utilities for scan results.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL