Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.git
.github
.gitignore
*.md
.env
.env.*
Dockerfile
.dockerignore
coverage.out
docs/
deploy/
api/
go.work
go.work.sum
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# inspect environment variables — copy to .env and fill in
# LLM API key for AI-powered scanning (used by llm_scanner.go)
INSPECT_LLM_API_KEY=
INSPECT_LLM_PROVIDER=openai
# Chromium path override (optional — leave empty to use bundled chromium)
CHROME_PATH=
63 changes: 63 additions & 0 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: Docker

on:
push:
branches: [main]
tags: ["v*"]
pull_request:
branches: [main]
paths:
- "Dockerfile"
- "**.go"
- "go.mod"
- "go.sum"

permissions:
contents: read
packages: write

env:
REGISTRY: ghcr.io
IMAGE_NAME: graycodeai/inspect

jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to GHCR
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha,prefix=sha-

- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
VERSION=${{ github.ref_name }}
COMMIT=${{ github.sha }}
BUILD_DATE=${{ github.event.head_commit.timestamp }}
39 changes: 39 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
FROM golang:1.26.3-alpine AS builder

RUN apk add --no-cache git ca-certificates chromium tzdata

WORKDIR /build
COPY go.mod go.sum ./
RUN go mod download && go mod verify

COPY . .
ARG VERSION=dev
ARG COMMIT=none
ARG BUILD_DATE=unknown

RUN CGO_ENABLED=0 GOOS=linux go build -trimpath \
-ldflags="-s -w \
-X main.Version=${VERSION} \
-X main.Commit=${COMMIT} \
-X main.BuildDate=${BUILD_DATE}" \
-o inspect-ci ./cmd/inspect-ci

RUN CGO_ENABLED=0 GOOS=linux go build -trimpath \
-ldflags="-s -w \
-X main.Version=${VERSION} \
-X main.Commit=${COMMIT} \
-X main.BuildDate=${BUILD_DATE}" \
-o inspect-action ./cmd/inspect-action

FROM alpine:3.21
# chromium is required for browser-based checks
RUN apk add --no-cache ca-certificates tini chromium && \
adduser -D -u 1000 inspect

COPY --from=builder /build/inspect-ci /usr/local/bin/inspect-ci
COPY --from=builder /build/inspect-action /usr/local/bin/inspect-action
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo

USER inspect
ENTRYPOINT ["tini", "--", "inspect-ci"]
CMD ["--help"]
81 changes: 81 additions & 0 deletions api/openapi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
openapi: "3.1.0"
info:
title: inspect — Security Auditor Tool Reference
description: |
inspect is a website security, accessibility, and SEO auditor.
It operates as a CLI tool, GitHub Action, and MCP server (stdio).

This document describes the MCP tool surface as a machine-readable reference.
inspect does NOT expose an HTTP server — all communication is via stdio MCP
or direct Go library calls.
version: "0.1.0"
license:
name: MIT
url: https://github.com/GrayCodeAI/inspect/blob/main/LICENSE
contact:
url: https://github.com/GrayCodeAI/inspect

# No servers section — inspect has no HTTP API.
# MCP tools are documented below using x-mcp-tool extensions.

tags:
- name: scan
description: Website scanning tools
- name: report
description: Report generation

x-mcp-server:
transport: stdio
binary: inspect-ci
start_command: ["inspect-ci", "mcp"]

x-mcp-tools:
inspect_scan:
description: Crawl a URL and run security, accessibility, SEO, and performance checks
inputSchema:
type: object
required: [url]
properties:
url:
type: string
description: Target URL to scan (must be http:// or https://)
checks:
type: array
items:
type: string
enum: [security, links, forms, a11y, performance, seo]
description: Checks to run (default: all)
depth:
type: integer
default: 3
description: Maximum crawl depth
concurrency:
type: integer
default: 5
description: Number of concurrent crawlers
format:
type: string
enum: [text, json, sarif, html, markdown, junit]
default: json
fail_on:
type: string
enum: [info, low, medium, high, critical]
description: Minimum severity that counts as a failure

inspect_scan_dir:
description: Scan a local directory of HTML files
inputSchema:
type: object
required: [path]
properties:
path:
type: string
description: Absolute path to the HTML directory
checks:
type: array
items:
type: string
format:
type: string
enum: [text, json, sarif, html, markdown]
default: json
13 changes: 13 additions & 0 deletions deploy/docker/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
name: inspect

services:
inspect:
build:
context: ../../
dockerfile: Dockerfile
image: ghcr.io/graycodeai/inspect:dev
env_file:
- path: ../../.env.example
required: false
entrypoint: ["inspect-ci"]
command: ["--help"]
140 changes: 140 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
<div align="center">

# 🔍 inspect Architecture

**Website Security, Accessibility & SEO Auditor**

[![Go](https://img.shields.io/badge/Go-1.26+-00ADD8?logo=go)](https://go.dev/)
[![Protocol](https://img.shields.io/badge/Protocol-MCP-purple)]()

</div>

---

## 🎯 Overview

inspect is a website security auditing and crawling library for Go. It crawls sites concurrently, runs **security**, **accessibility**, **SEO**, and **performance** checks, and generates findings with severity levels and **CWE references**.

> 💡 Three modes: **Go library**, **CLI binary** (`inspect-ci`), and **GitHub Action**.

---

## 🧱 Components

```
inspect/
├── api/openapi.yaml 📜 MCP tool surface reference
├── cmd/
│ ├── inspect-ci/main.go 🖥️ CLI binary entry point
│ └── inspect-action/main.go ⚡ GitHub Action entry point
├── inspect.go 📤 Public API: Scan(), Finding, Report, Stats
├── check.go 🛡️ Checker interface, RuleCheck, RegisterCheck()
├── scanner.go 🔄 Crawler orchestration, check execution
├── options.go ⚙️ config, With* functions, presets
├── config.go 📋 .inspect.toml loading
├── sarif.go 📊 SARIF output formatter
├── ci_output.go 🖥️ CI-friendly terminal output
├── llm_scanner.go 🤖 AI-powered scanning
├── api_security.go 🔒 API endpoint security checks
├── dependency_check.go 📦 Dependency vulnerability checks
├── sbom.go 📋 SBOM generation
├── browser.go 🌐 Browser automation entry
├── browser/ 🖥️ Rod-based browser crawling
├── checks/
│ ├── security.go 🔒 CSP, HSTS, CORS headers
│ ├── accessibility.go ♿ ARIA violations
│ ├── tls.go 🔐 Certificate checks
│ ├── cookies.go 🍪 Cookie security flags
│ ├── headers.go 📋 Missing security headers
│ └── mixed_content.go ⚠️ Mixed content detection
├── mcp/ 🔌 MCP server (stdio transport)
└── internal/
├── crawler/ 🕷️ URL parsing, sitemap, robots.txt
├── check/ 🔄 Internal check runner
├── html/ 📄 HTML parsing utilities
└── report/ 📊 Output format implementations
```

---

## 📤 Public API

```go
// 🚀 One-shot scan
report, err := inspect.Scan(ctx, "https://example.com",
inspect.WithChecks("security", "a11y"),
inspect.WithDepth(3),
)

// 🔄 Reusable scanner
scanner := inspect.NewScanner(inspect.WithConcurrency(10))
report, err := scanner.Scan(ctx, "https://example.com")

// 🛡️ Custom Go check
inspect.RegisterCheck(myCheck)

// 📋 Declarative rule (no Go code)
inspect.RegisterRule(inspect.RuleCheck{
Name: "x-frame-options", Severity: inspect.High,
Check: inspect.HeaderMissing{Header: "X-Frame-Options"},
})
```

---

## ⚡ Presets

| Preset | Checks | Speed |
|--------|--------|:-----:|
| 🏃 `Quick` | links, security headers | Fast |
| 📊 `Standard` | links, security, forms, a11y | Medium |
| 🔬 `Deep` | all checks, depth 10 | Slow |
| 🔒 `SecurityOnly` | security, TLS, cookies, headers | Fast |
| 🤖 `CI` | all checks, fail on Medium+ | Medium |

---

## 🔌 MCP Server

```bash
inspect-ci mcp # 📡 stdio transport (add to agent MCP config)
```

**Tools:** `inspect_scan` — crawl URL and run checks · `inspect_scan_dir` — scan local HTML directory

---

## 🐙 GitHub Action

```yaml
- uses: GrayCodeAI/inspect@v0.4.0
with:
url: https://example.com
checks: security,a11y
fail-on: high
```

---

## 🔎 Findings

Each finding includes:

| Field | Description |
|-------|-------------|
| `Check` | Which check produced this finding |
| `Severity` | 🟢 Info · 🟡 Low · 🟠 Medium · 🔴 High · 🟥 Critical |
| `URL` | Page where the issue was found |
| `Message` | Human-readable description |
| `Evidence` | Snippet of the problematic content |
| `CWE` | CWE reference (required for security findings) |
| `Confidence` | 0.0–1.0 score |

---

## 🛡️ ReDoS Protection

All user-supplied regex patterns go through:
- `compileWithTimeout()` — **1s** compilation limit
- `matchWithTimeout()` — **100ms** match limit
- `checkRegexComplexity()` — rejects nested quantifiers before compilation
Loading