diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..39f1a41 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,67 @@ +# EditorConfig — https://editorconfig.org +# Canonical eco-wide template (.shared-templates/editorconfig.tmpl). + +root = true + +# Default for everything. +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 4 + +# Go uses tabs by convention. +[*.go] +indent_style = tab +indent_size = 4 + +# Python — PEP 8. +[*.py] +indent_size = 4 + +# TypeScript / JavaScript — 2 spaces, ecosystem default. +[*.{ts,tsx,js,jsx,mjs,cjs}] +indent_size = 2 + +# Web assets. +[*.{html,css,scss}] +indent_size = 2 + +# YAML — 2 spaces (ecosystem standard, GitHub Actions, k8s, etc.). +[*.{yml,yaml}] +indent_size = 2 + +# JSON / JSONC. +[*.{json,jsonc}] +indent_size = 2 + +# TOML. +[*.toml] +indent_size = 2 + +# Markdown — 2 spaces, preserve trailing whitespace (used for line breaks). +[*.md] +trim_trailing_whitespace = false +indent_size = 2 + +# Shell scripts. +[*.{sh,bash,zsh,fish}] +indent_size = 4 + +# Makefiles must use tabs. +[{Makefile,*.mk}] +indent_style = tab + +# Dockerfiles. +[Dockerfile*] +indent_size = 4 + +# GitHub Actions workflows — 2 spaces. +[.github/**/*.{yml,yaml}] +indent_size = 2 + +# Config files. +[*.{cfg,ini,conf}] +indent_size = 4 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..3342e8f --- /dev/null +++ b/.gitattributes @@ -0,0 +1,86 @@ +# Canonical eco-wide .gitattributes template (.shared-templates/gitattributes.tmpl). +# Auto-detect text files and normalise line endings to LF. + +* text=auto eol=lf + +# --- Source code ----------------------------------------------------------- +*.go text eol=lf diff=golang +*.py text eol=lf diff=python +*.ts text eol=lf +*.tsx text eol=lf +*.js text eol=lf +*.jsx text eol=lf +*.mjs text eol=lf +*.cjs text eol=lf +*.rs text eol=lf diff=rust + +# --- Shell + config -------------------------------------------------------- +*.sh text eol=lf +*.bash text eol=lf +*.toml text eol=lf +*.yaml text eol=lf +*.yml text eol=lf +*.json text eol=lf linguist-language=JSON +*.jsonc text eol=lf linguist-language=JSON +*.cff text eol=lf + +# --- Documentation --------------------------------------------------------- +*.md text eol=lf diff=markdown +*.txt text eol=lf + +# --- Build / packaging ---------------------------------------------------- +Makefile text eol=lf +*.mk text eol=lf +Dockerfile* text eol=lf +docker-compose*.yml text eol=lf +.github/**/*.yml text eol=lf +.github/**/*.yaml text eol=lf + +# --- Generated artefacts (mark as such for diffs and language stats) ------ +go.mod text eol=lf linguist-generated +go.sum text eol=lf linguist-generated +*.pb.go linguist-generated +*_generated.go linguist-generated +package-lock.json linguist-generated +pnpm-lock.yaml linguist-generated +yarn.lock linguist-generated + +# --- Vendored / external sources ------------------------------------------ +vendor/** linguist-vendored +node_modules/** linguist-vendored +testdata/** linguist-vendored +benchmarks/data/** linguist-vendored + +# --- Binary files (do not text-normalise) --------------------------------- +*.exe binary +*.dll binary +*.so binary +*.dylib binary +*.a binary +*.o binary +*.db binary +*.sqlite binary +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.ico binary +*.svg text eol=lf +*.pdf binary +*.zip binary +*.tar.gz binary +*.tgz binary +*.whl binary + +# --- Source archive hygiene (excluded from `git archive`) ----------------- +.github export-ignore +.shared-templates export-ignore +.gitattributes export-ignore +.gitignore export-ignore +.editorconfig export-ignore +.golangci.yml export-ignore +.goreleaser.yml export-ignore +.goreleaser.yaml export-ignore +testdata/ export-ignore +benchmarks/ export-ignore +e2e/ export-ignore diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..a13f7d1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,108 @@ +name: Bug report +description: Something is broken or behaving unexpectedly. +title: "bug: " +labels: ["bug", "triage"] + +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to file a bug report. Please fill in as much + of the form as you can — the more we know, the faster we can fix it. + + Before submitting: + - Search [existing issues](https://github.com/GrayCodeAI/inspect/issues) to avoid duplicates. + - If this is a security issue, please **do not** file a public issue. See `SECURITY.md`. + + - type: textarea + id: what-happened + attributes: + label: What happened? + description: A clear, concise description of the bug. + placeholder: When I scan , I expected X but got Y. + validations: + required: true + + - type: dropdown + id: surface + attributes: + label: Surface + description: How are you using inspect? + options: + - "Library API (`inspect.Scan`, `inspect.NewScanner`, etc.)" + - "MCP (`inspect_scan`, `inspect_scan_dir` tools)" + - "GitHub Action (`.github/action.yml`)" + - "SARIF output" + - "Browser-based checks (`browser/` sub-module)" + - "CVE / dependency database" + validations: + required: true + + - type: textarea + id: reproduce + attributes: + label: Steps to reproduce + description: | + Minimal Go snippet, MCP call, or scan target that reliably + reproduces the problem. **Use a synthetic test site or a public + URL — never include credentials or private URLs.** + render: go + placeholder: | + report, err := inspect.Scan(ctx, "https://example.com", inspect.Standard) + // ^ wrong finding / missed finding / panic / SSRF / etc. + validations: + required: true + + - type: textarea + id: expected + attributes: + label: Expected behavior + description: What did you expect to happen instead? + validations: + required: true + + - type: textarea + id: false-positive + attributes: + label: If this is a false positive / false negative + description: | + Paste the relevant finding (or the missing one), and explain why + you think the verdict is wrong. Include the snippet of the page + / response that triggered (or should have triggered) it. + + - type: input + id: inspect-version + attributes: + label: inspect version + description: Output of `inspect version` or the git SHA you built from. + placeholder: "0.2.0" + validations: + required: true + + - type: input + id: go-version + attributes: + label: Go version + description: Output of `go version`. + placeholder: "go version go1.26.1 darwin/arm64" + validations: + required: true + + - type: textarea + id: logs + attributes: + label: Logs / output + description: | + Paste relevant output. **Redact any secrets, internal URLs, or + identifying info first.** + render: shell + + - type: checkboxes + id: confirm + attributes: + label: Confirmation + options: + - label: I searched existing issues and did not find a duplicate. + required: true + - label: I redacted any secrets, internal URLs, or PII from logs and reproductions. + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..f93a308 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Security vulnerability + url: https://github.com/GrayCodeAI/inspect/security/advisories/new + about: Please report security issues privately via a GitHub Security Advisory. See SECURITY.md. + - name: Question / discussion + url: https://github.com/GrayCodeAI/inspect/discussions + about: Have a question or want to discuss an idea? Open a discussion instead of an issue. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..8bc3c10 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,72 @@ +name: Feature request +description: Suggest an improvement, a new check, or a new integration. +title: "feat: " +labels: ["enhancement", "triage"] + +body: + - type: markdown + attributes: + value: | + Thanks for proposing a feature. inspect is a security/quality scanner + for websites and dependencies. Every feature is evaluated against + whether it serves **a single developer** running their coding agent + locally — i.e. it improves precision/recall, lowers latency, or + simplifies integration. + + Before submitting: + - Search [existing issues](https://github.com/GrayCodeAI/inspect/issues) to avoid duplicates. + - For new check categories, please open a discussion first. + + - type: dropdown + id: kind + attributes: + label: Kind of feature + description: What flavour of change is this? + options: + - "Security check (header, TLS, cookies, SSRF, mixed-content, etc.)" + - "Accessibility check (WCAG, ARIA, contrast)" + - "SEO / structured-data check" + - "Performance check" + - "Browser-based / dynamic check (`browser/`)" + - "Output format (JSON, SARIF, JUnit, HTML, terminal)" + - "Configuration (`.inspect.toml`, options)" + - "MCP server / GitHub Action / tooling" + validations: + required: true + + - type: textarea + id: problem + attributes: + label: What problem are you trying to solve? + description: Describe the user problem first. Solutions can come later. + placeholder: When I scan , inspect misses Y / surfaces too many Z. + validations: + required: true + + - type: textarea + id: proposal + attributes: + label: Proposed solution + description: How would you like inspect to behave? Library snippet, MCP tool shape, config sample. + validations: + required: true + + - type: textarea + id: alternatives + attributes: + label: Alternatives considered + description: | + What did you try? What do other scanners (gosec, trivy, snyk, grype, + osv-scanner, bearer, gitleaks, trufflehog, checkov, tfsec) do? Why + isn't that enough? + + - type: checkboxes + id: principles + attributes: + label: Solo-developer fit + description: inspect avoids enterprise scope. Confirm this feature respects that. + options: + - label: Works with zero configuration (sensible defaults). + - label: Does not require a network call beyond the scan target itself. + - label: Does not weaken SSRF / egress protections. + - label: Has an escape hatch (override via option, env, or config). diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..7d67d76 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,91 @@ + + +## Summary + + + +## Changes + + + +- + +## Scan-quality impact + + + +## SARIF compatibility + + + +## SSRF & egress safety + + + +## Testing + + + +```text +$ make test +... +$ make lint +... +``` + +## Checklist + +- [ ] Commits follow [Conventional Commits](https://www.conventionalcommits.org/) + (`feat:`, `fix:`, `perf:`, `refactor:`, `docs:`, `test:`, etc.) +- [ ] `make build` passes +- [ ] `make lint` passes +- [ ] `make test-race` passes locally +- [ ] New or changed code has tests +- [ ] Public APIs in `inspect.go`, `scanner.go`, etc. have godoc comments +- [ ] `CHANGELOG.md` updated under `## [Unreleased]` if user-visible +- [ ] No regression in default scan output on representative fixtures +- [ ] SARIF output (if touched) validates against the 2.1.0 schema +- [ ] SSRF protection (if touched) — private-IP blocking, redirect + filtering, scheme allowlists — is preserved +- [ ] No secrets, real-customer URLs, or PII in test fixtures +- [ ] No `Co-authored-by:` trailers (this is solo-developer work) diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..bb260cc --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,49 @@ +version: 2 +updates: + - package-ecosystem: gomod + directory: / + schedule: + interval: weekly + day: monday + open-pull-requests-limit: 5 + labels: + - dependencies + - go + commit-message: + prefix: "chore(deps)" + include: scope + groups: + mark3labs-mcp: + patterns: + - "github.com/mark3labs/mcp-go*" + + - package-ecosystem: gomod + directory: /browser + schedule: + interval: weekly + day: monday + open-pull-requests-limit: 3 + labels: + - dependencies + - go + - browser + commit-message: + prefix: "chore(browser-deps)" + include: scope + groups: + rod: + patterns: + - "github.com/go-rod/*" + + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + day: monday + open-pull-requests-limit: 3 + labels: + - dependencies + - github-actions + commit-message: + prefix: "chore(ci)" + include: scope diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5352ecf..8abb3cb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,3 +1,21 @@ +# Canonical CI workflow for hawk-eco Go repos. +# Source of truth: .shared-templates/workflows/go-ci.yml.tmpl +# +# Two deployment models: +# +# 1. NOW — render this template inline into each repo's +# .github/workflows/ci.yml. Every repo has identical content. +# +# 2. LATER — once GrayCodeAI/.github exists as a central repo, move this +# file to GrayCodeAI/.github/.github/workflows/go-ci.yml with +# `on: workflow_call:`. Each repo's ci.yml becomes a 5-line caller: +# +# name: CI +# on: { push: { branches: [main] }, pull_request: } +# jobs: +# ci: +# uses: GrayCodeAI/.github/.github/workflows/go-ci.yml@main + name: CI on: @@ -6,50 +24,138 @@ on: pull_request: branches: [main, dev] +permissions: + contents: read + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + GO_VERSION: "1.26.1" + jobs: - test: - name: Test + # ------------------------------------------------------------------------- + # Format + vet — fastest, fail fast. + # ------------------------------------------------------------------------- + fmt-vet: + name: fmt + vet runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 with: - go-version: "1.26.1" + go-version: ${{ env.GO_VERSION }} cache: true - - - name: Build - run: go build ./... - - - name: Vet + - name: gofumpt diff + run: | + go install mvdan.cc/gofumpt@latest + out=$(gofumpt -l .) + if [ -n "$out" ]; then + echo "::error::gofumpt would reformat the following files:" + echo "$out" + exit 1 + fi + - name: go vet run: go vet ./... - - name: Test - run: go test ./... -race -count=1 -timeout=60s - - - name: Coverage - run: go test ./... -coverprofile=coverage.out -covermode=atomic + # ------------------------------------------------------------------------- + # Lint — golangci-lint covers most static checks. + # ------------------------------------------------------------------------- + lint: + name: lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + cache: true + - uses: golangci/golangci-lint-action@v7 + with: + version: v2.1.0 + install-mode: goinstall + verify: false + args: --timeout=5m + # ------------------------------------------------------------------------- + # Tests with race detector + coverage upload. + # ------------------------------------------------------------------------- + test: + name: test (race + cover) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + cache: true + - name: Tidy check + run: | + go mod tidy + if ! git diff --quiet; then + echo "::error::go.mod / go.sum out of date — run 'go mod tidy' and commit" + git diff + exit 1 + fi + - name: Test + run: go test ./... -race -count=1 -coverprofile=coverage.out -covermode=atomic -timeout=180s + - name: Coverage summary + run: go tool cover -func=coverage.out | tail -1 - name: Upload coverage uses: actions/upload-artifact@v4 with: name: coverage path: coverage.out - lint: - name: Lint + # ------------------------------------------------------------------------- + # Security scan — vulnerability database + (optional) gosec. + # ------------------------------------------------------------------------- + security: + name: security runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 with: - go-version: "1.26.1" + go-version: ${{ env.GO_VERSION }} cache: true + - name: govulncheck + run: | + go install golang.org/x/vuln/cmd/govulncheck@latest + govulncheck ./... + - name: gosec (advisory) + continue-on-error: true + run: | + go install github.com/securego/gosec/v2/cmd/gosec@latest + gosec -exclude=G104,G301,G302,G304,G306 ./... - - name: golangci-lint - uses: golangci/golangci-lint-action@v7 + # ------------------------------------------------------------------------- + # Cross-platform build matrix — only for repos that produce a binary. + # Repos that are pure libraries can keep this job (it'll just `go build ./...`) + # or remove it locally. + # ------------------------------------------------------------------------- + build: + name: build (${{ matrix.goos }}/${{ matrix.goarch }}) + runs-on: ubuntu-latest + needs: [fmt-vet, lint, test] + strategy: + fail-fast: false + matrix: + goos: [linux, darwin, windows] + goarch: [amd64, arm64] + exclude: + - goos: windows + goarch: arm64 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 with: - version: v2.1.0 - install-mode: goinstall - verify: false + go-version: ${{ env.GO_VERSION }} + cache: true + - name: Build + env: + GOOS: ${{ matrix.goos }} + GOARCH: ${{ matrix.goarch }} + CGO_ENABLED: "0" + run: go build ./... diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 0000000..639f55f --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,43 @@ +# Canonical release-please workflow for hawk-eco repos. +# Opens / updates a release PR on every push to main; on merge of that PR, +# tags the new release. The tag triggers goreleaser (separate workflow). +# +# Source of truth: .shared-templates/release-please.yml.tmpl at the eco root. + +name: release-please + +on: + push: + branches: [main] + +permissions: + contents: write + pull-requests: write + issues: write + +concurrency: + group: release-please-${{ github.ref }} + cancel-in-progress: false + +jobs: + release-please: + runs-on: ubuntu-latest + steps: + - name: Run release-please + id: release + uses: googleapis/release-please-action@v4 + with: + config-file: release-please-config.json + manifest-file: .release-please-manifest.json + token: ${{ secrets.RELEASE_PLEASE_TOKEN || secrets.GITHUB_TOKEN }} + + - name: Summary + if: always() + run: | + if [[ "${{ steps.release.outputs.release_created }}" == "true" ]]; then + echo "Released ${{ steps.release.outputs.tag_name }}." >> $GITHUB_STEP_SUMMARY + elif [[ "${{ steps.release.outputs.pr }}" != "" ]]; then + echo "Updated release PR: ${{ steps.release.outputs.pr }}" >> $GITHUB_STEP_SUMMARY + else + echo "No release-relevant changes detected." >> $GITHUB_STEP_SUMMARY + fi diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 0000000..2be9c43 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "0.2.0" +} diff --git a/CHANGELOG.md b/CHANGELOG.md index d6635b7..ba3f1b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,48 @@ All notable changes to inspect are documented here. Format: [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) +This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +--- + +## [Unreleased] + +### Changed +- **Version re-baselined to `0.2.0`** in + `internal/report/sarif.go` (`const inspectVersion`, used as + `tool.driver.version` in SARIF output) and `mcp/server.go` + (advertised MCP server version). Aligns inspect with the rest of + the hawk-eco ecosystem (`hawk`, `tok`, `eyrie`, `yaad`, `trace`, + `sight`). + - Note: the previous values were inconsistent (`inspectVersion` + was `"1.0.0"` while the MCP server advertised `"0.1.0"`); both + now agree on `"0.2.0"`. + - The SARIF spec version (`sarifVersion = "2.1.0"`) is unchanged + — that's a different field that identifies the SARIF format, + not the tool. + +### Added — Production hygiene (top-50 OSS parity) +- `CODE_OF_CONDUCT.md` — Contributor Covenant 2.1. +- `.gitattributes` — LF normalization, binary detection, GitHub + linguist hint to collapse `go.sum` in PR diffs. +- `.editorconfig` — UTF-8, LF, final newline, trim trailing whitespace, + tabs for Go + Makefile, 2-space indent for YAML/JSON/TOML, no-trim + for Markdown. +- `.github/dependabot.yml` — weekly `gomod` (root + `browser/` + sub-module) + `github-actions` updates. +- `.github/PULL_REQUEST_TEMPLATE.md` — Summary / Changes / Scan- + quality impact / SARIF compatibility / SSRF & egress safety / + Testing / Checklist. +- `.github/ISSUE_TEMPLATE/bug_report.yml` — surface dropdown + (library API / MCP / SARIF / GitHub Action / browser checks / + CVE database) and false-positive textarea. +- `.github/ISSUE_TEMPLATE/feature_request.yml` — `kind` selector + covering 8 functional areas (security checks / accessibility / + SEO / performance / browser / output formats / config / tooling) + and solo-dev fit checks. +- `.github/ISSUE_TEMPLATE/config.yml` — routes security to + advisories, questions to discussions, blocks blank issues. + --- ## [0.4.0] — 2026-05-08 diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..64b412b --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,29 @@ +# CODEOWNERS for inspect (web scanner library) +* @GrayCodeAI/maintainers + +# Scanner core +/inspect.go @GrayCodeAI/scanner-team +/scanner.go @GrayCodeAI/scanner-team +/check.go @GrayCodeAI/scanner-team +/checks/ @GrayCodeAI/scanner-team +/internal/check/ @GrayCodeAI/scanner-team +/internal/crawler/ @GrayCodeAI/scanner-team + +# Security / dependency checks +/dependency_check.go @GrayCodeAI/security-team +/api_security.go @GrayCodeAI/security-team + +# Output formats +/internal/report/ @GrayCodeAI/scanner-team +/report.go @GrayCodeAI/scanner-team + +# Versioning +/VERSION @GrayCodeAI/maintainers +/version.go @GrayCodeAI/maintainers + +# CI / release +/.github/ @GrayCodeAI/devops-team +/Makefile @GrayCodeAI/devops-team + +# Documentation +*.md @GrayCodeAI/docs-team diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..4a76c71 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,60 @@ +# Code of Conduct + +## Our pledge + +We — the maintainers and contributors of the inspect project — pledge to +make participation in our community a harassment-free experience for everyone, +regardless of age, body size, visible or invisible disability, ethnicity, sex +characteristics, gender identity and expression, level of experience, +education, socio-economic status, nationality, personal appearance, race, +religion, or sexual identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our standards + +Examples of behaviour that contributes to a positive environment: + +- Demonstrating empathy and kindness toward other people. +- Being respectful of differing opinions, viewpoints, and experiences. +- Giving and gracefully accepting constructive feedback. +- Accepting responsibility, apologising to those affected by mistakes, and + learning from the experience. +- Focusing on what is best not just for us as individuals, but for the + overall community. + +Examples of unacceptable behaviour: + +- The use of sexualised language or imagery, and sexual attention or advances. +- Trolling, insulting or derogatory comments, and personal or political + attacks. +- Public or private harassment. +- Publishing others' private information, such as a physical or email + address, without their explicit permission. +- Other conduct which could reasonably be considered inappropriate in a + professional setting. + +## Enforcement + +Community leaders are responsible for clarifying and enforcing our standards +of acceptable behaviour, and will take appropriate and fair corrective +action in response to any behaviour they deem inappropriate, threatening, +offensive, or harmful. + +Instances of abusive, harassing, or otherwise unacceptable behaviour may be +reported to the maintainers via the contact in `SECURITY.md` or by opening a +confidential GitHub Security Advisory at +. All +complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of +the reporter of any incident. + +## Attribution + +This Code of Conduct is adapted from the +[Contributor Covenant, version 2.1](https://www.contributor-covenant.org/version/2/1/code_of_conduct.html). + +For answers to common questions about this code of conduct, see the +Contributor Covenant FAQ at . diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 13af967..8b61378 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,54 +1,114 @@ # Contributing to inspect -## Setup +Thanks for your interest! This guide covers the conventions used across the +hawk-eco. The eco-wide standards (versioning, release tooling, repo layout) +are defined in . + +## Quick start + +1. Fork the repo and create a feature branch off `main`: + ```bash + git checkout -b feat/short-description + ``` +2. Make your changes in small, focused commits. +3. Run the full local check before pushing: + ```bash + make ci + ``` +4. Open a pull request. CI will re-run the same checks plus security + scanning, race-detector tests, and (where applicable) integration tests. + +## Build & test + +This repo uses the standardised hawk-eco Makefile targets. Run `make help` +for the full list. The most common targets: + +| Target | What it does | +| ------------------- | ------------------------------------------------ | +| `make build` | Build the binary / verify the library compiles | +| `make test` | Run unit tests | +| `make test-race` | Run unit tests with the race detector | +| `make cover` | Generate a coverage report | +| `make lint` | Run the linter (`golangci-lint` / `ruff`) | +| `make fmt` | Format source files | +| `make vet` | Run `go vet` / `mypy` | +| `make security` | Run `govulncheck` / `pip-audit` | +| `make ci` | Run everything CI runs (the gate before pushing) | + +## Commit message convention + +We use [Conventional Commits](https://www.conventionalcommits.org/). This +isn't cosmetic — release-please reads commit messages to bump the `VERSION` +file and generate the CHANGELOG, so getting them right matters. -```bash -git clone https://github.com/GrayCodeAI/inspect.git -cd inspect -make test ``` +(): -## Requirements + -- Go 1.26+ -- For browser checks: Chrome/Chromium (rod auto-downloads) + +``` + +**Types:** + +- `feat:` — a new feature (triggers a minor version bump) +- `fix:` — a bug fix (triggers a patch version bump) +- `perf:` — performance improvement +- `refactor:` — code restructure with no behaviour change +- `docs:` — documentation only +- `test:` — adding or fixing tests +- `build:` — build system or dependencies +- `ci:` — CI configuration +- `chore:` — anything else (no release effect) +- `revert:` — reverts a previous commit + +**Breaking changes:** add `!` after the type/scope or include `BREAKING +CHANGE:` in the footer. This triggers a major version bump. -## Development +Examples: -```bash -make test # Run tests -make test-race # With race detector -make cover # Coverage report -make lint # Static analysis -make bench # Benchmarks ``` +feat(client): add streaming retry with exponential backoff +fix: handle empty response body in chat handler +refactor!: rename ClientV1 to Client (BREAKING CHANGE) +``` + +## Pull request checklist + +Before requesting review: -## Adding a New Check +- [ ] `make ci` passes locally. +- [ ] New behaviour has tests; bug fixes have a regression test. +- [ ] `CHANGELOG.md` entries are **not** edited manually — release-please + generates them from your commit messages. +- [ ] The `VERSION` file is **not** edited manually — release-please bumps + it on release. +- [ ] Public API changes have updated doc comments. +- [ ] No secrets, API keys, or PII in code, comments, tests, or fixtures. -### Custom check (Go code) +## Code review etiquette -1. Create a file in `internal/check/` -2. Implement the `Checker` interface -3. Register in `internal/check/registry.go` -4. Add tests +- Reviewers focus on correctness, design, and tests; formatting is + enforced by tooling, not humans. +- Authors respond to every comment (resolved, addressed, or politely + declined with rationale) — no silent dismissals. +- Squash-merge by default; the PR title becomes the commit (so it must + be a valid Conventional Commit message). +- One approving review from a CODEOWNERS-listed reviewer is required. -### Declarative rule (no Go code) +## Reporting bugs -1. Add a `RuleCheck` struct in `internal/check/rules.go` -2. Specify: HeaderMatch/HeaderMissing/BodyMatch/BodyMissing -3. Add tests +Open an issue using the bug-report template. Include the `inspect` +version (`inspect --version` for binaries, `inspect.Version` for +libraries — see this repo's `VERSION` file), reproduction steps, expected +behaviour, and actual behaviour. -## Guidelines +## Reporting security issues -- All tests must pass with `-race` flag -- Use `t.Parallel()` in tests -- SSRF protection must remain enabled by default -- Rate limiting must be respected (don't remove defaults) -- Add tests for new checks +**Do not open a public issue.** See [SECURITY.md](./SECURITY.md) for +private reporting channels. -## Pull Requests +## License -1. Open an issue first for significant changes -2. Run `make all` before submitting -3. Include test coverage -4. Update CHANGELOG.md +By contributing, you agree that your contributions will be licensed under +the same license as this repo (see [LICENSE](./LICENSE)). diff --git a/Makefile b/Makefile index f45c4cf..636e26e 100644 --- a/Makefile +++ b/Makefile @@ -1,31 +1,112 @@ -.PHONY: all build test test-race cover vet lint bench clean +# Canonical hawk-eco Makefile for Go library repos. +# Source of truth: .shared-templates/Makefile.library.tmpl at the eco root. +# Placeholders rendered per repo: inspect. -all: vet test build +# --------------------------------------------------------------------------- +# Project metadata +# --------------------------------------------------------------------------- +NAME := inspect -build: - go build ./... +# --------------------------------------------------------------------------- +# Versioning — sourced from VERSION file; falls back to git describe. +# See https://github.com/GrayCodeAI/hawk/blob/main/VERSIONING.md. +# --------------------------------------------------------------------------- +VERSION ?= $(shell cat VERSION 2>/dev/null | head -n1 | tr -d '[:space:]' || git describe --tags --always --dirty 2>/dev/null || echo "dev") +COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo "none") +DATE := $(shell date -u '+%Y-%m-%dT%H:%M:%SZ') -test: - go test ./... -timeout 60s +# --------------------------------------------------------------------------- +# Tooling — pinned, install if missing. +# --------------------------------------------------------------------------- +GOBIN_DIR := $(shell go env GOPATH)/bin +GOLANGCI := $(GOBIN_DIR)/golangci-lint +GOFUMPT := $(GOBIN_DIR)/gofumpt +GOIMPORTS := $(GOBIN_DIR)/goimports +GOVULNCHECK := $(GOBIN_DIR)/govulncheck -test-race: - go test -race ./... -timeout 60s +# --------------------------------------------------------------------------- +# Phony declarations (alphabetical). +# --------------------------------------------------------------------------- +.PHONY: all bench build ci clean cover fmt help lint lint-fix \ + security test test-10x test-race tidy version vet -cover: - go test -coverprofile=coverage.out ./... - go tool cover -func=coverage.out - @rm -f coverage.out +# --------------------------------------------------------------------------- +# Default target. +# --------------------------------------------------------------------------- +all: lint test build ## Default — lint, test, build. -vet: +# --------------------------------------------------------------------------- +# Build (verify the library compiles). +# --------------------------------------------------------------------------- +build: ## Verify the library compiles. + CGO_ENABLED=0 go build ./... + +# --------------------------------------------------------------------------- +# Tests. +# --------------------------------------------------------------------------- +test: ## Run unit tests. + go test ./... -count=1 -timeout=120s + +test-race: ## Run unit tests with the race detector. + go test ./... -race -count=1 -timeout=180s + +test-10x: ## Run tests 10 times to surface flakes. + go test ./... -race -count=10 -timeout=600s + +cover: ## Generate a coverage report (coverage.out + coverage.html). + go test ./... -race -coverprofile=coverage.out -covermode=atomic -timeout=180s + @go tool cover -func=coverage.out | grep "^total:" + @go tool cover -html=coverage.out -o coverage.html + @echo "Coverage report: coverage.html" + +bench: ## Run benchmarks. + go test ./... -bench=. -benchmem -count=3 -timeout=300s + +# --------------------------------------------------------------------------- +# Quality gates. +# --------------------------------------------------------------------------- +fmt: ## Format source files (gofumpt + goimports). + @command -v $(GOFUMPT) >/dev/null 2>&1 || (echo "install: go install mvdan.cc/gofumpt@latest" && exit 1) + @command -v $(GOIMPORTS) >/dev/null 2>&1 || (echo "install: go install golang.org/x/tools/cmd/goimports@latest" && exit 1) + $(GOFUMPT) -w . + $(GOIMPORTS) -w . + +vet: ## Run go vet. go vet ./... -lint: - @which staticcheck > /dev/null 2>&1 || (echo "install: go install honnef.co/go/tools/cmd/staticcheck@latest" && exit 1) - staticcheck ./... +lint: ## Run golangci-lint. + @command -v $(GOLANGCI) >/dev/null 2>&1 || (echo "install: go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest" && exit 1) + $(GOLANGCI) run ./... --timeout=5m + +lint-fix: ## Run golangci-lint with --fix. + @command -v $(GOLANGCI) >/dev/null 2>&1 || (echo "install: go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest" && exit 1) + $(GOLANGCI) run ./... --fix --timeout=5m + +security: ## Run govulncheck. + @command -v $(GOVULNCHECK) >/dev/null 2>&1 || (echo "install: go install golang.org/x/vuln/cmd/govulncheck@latest" && exit 1) + $(GOVULNCHECK) ./... + +tidy: ## Tidy go.mod / go.sum. + go mod tidy + go mod verify + +# --------------------------------------------------------------------------- +# Composite gate used by CI and pre-push. +# --------------------------------------------------------------------------- +ci: tidy fmt vet lint test-race security ## Run everything CI runs. + @echo "All CI checks passed." + +# --------------------------------------------------------------------------- +# Misc. +# --------------------------------------------------------------------------- +version: ## Print the version that will be embedded. + @echo "Version: $(VERSION)" + @echo "Commit: $(COMMIT)" + @echo "Date: $(DATE)" -bench: - go test -bench=. -benchmem ./... +clean: ## Remove build artefacts. + rm -rf coverage.out coverage.html + go clean -testcache -clean: - go clean - rm -f coverage.out +help: ## Show this help. + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-15s\033[0m %s\n", $$1, $$2}' diff --git a/SECURITY.md b/SECURITY.md index bd6310e..43320e1 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,29 +1,71 @@ -# Security Policy +# Security Policy — inspect -## Supported Versions +## Supported versions -| Version | Supported | -|---------|-----------| -| 0.4.x | Yes | -| 0.2.x | Yes | -| < 0.2 | No | +We support the latest minor version on each `0.x` line, and the latest two +minor versions once `1.x` ships. Older versions receive critical-severity +fixes only on a best-effort basis. -## Reporting a Vulnerability +The current canonical version is the contents of the [`VERSION`](./VERSION) +file at the repo root. See [`VERSIONING.md`](https://github.com/GrayCodeAI/hawk/blob/main/VERSIONING.md) +for the eco-wide versioning scheme. -**Do NOT open a public GitHub issue for security vulnerabilities.** +## Reporting a vulnerability -Email: security@graycode.ai +**Do not open a public GitHub issue for security vulnerabilities.** Instead: -### Response Timeline -- Acknowledgment: 48 hours -- Initial assessment: 5 business days -- Fix: 7-30 days depending on severity +1. Open a private [GitHub Security Advisory](https://github.com/GrayCodeAI/inspect/security/advisories/new), **or** +2. Email `security@graycode.ai` with the details below. -## Security Considerations +Include in your report: -- inspect makes HTTP requests to user-specified URLs -- SSRF protection blocks private IP ranges by default (WithBlockPrivateIPs) -- Rate limiting prevents accidental DoS of target sites -- No credentials are stored by the library -- Browser engine (rod) is an optional sub-module with separate security surface -- Crawled page content is processed locally, never transmitted externally +- A description of the vulnerability and the affected component. +- Steps to reproduce, ideally with a minimal proof-of-concept. +- The version (`VERSION` file or git SHA) you tested against. +- The potential impact and any suggested mitigation. + +**Response targets:** + +- Initial acknowledgement: within **48 hours**. +- Triage and severity assessment: within **5 business days**. +- Coordinated fix and disclosure: within **30 days** for high/critical, **90 + days** for medium/low (per industry-standard responsible disclosure). + +## Disclosure policy + +We follow [coordinated vulnerability disclosure](https://en.wikipedia.org/wiki/Coordinated_vulnerability_disclosure): + +- Reporters receive credit in the advisory and CHANGELOG (unless they opt + out). +- We request that reporters refrain from public disclosure until a fix has + been released or the disclosure deadline above has elapsed. +- We will not pursue legal action against good-faith researchers acting + within this policy. + +## Security practices in this repo + +- **Dependency monitoring:** automated via Dependabot (see + `.github/dependabot.yml`). +- **Static analysis:** `golangci-lint` / `ruff` / `mypy` enforced in CI. +- **Vulnerability scanning:** `govulncheck` (Go) / `pip-audit` (Python) run + on every CI build. +- **Lockfiles:** `go.sum` / `pnpm-lock.yaml` / `pyproject.toml` are pinned + and committed. +- **Reproducible builds:** release artefacts ship with SHA-256 checksums via + goreleaser. +- **No secrets in source:** API keys are configuration, not constants. Pre- + commit hooks block accidental secret commits. + +## Scope + +This policy covers the code in this repository and the release artefacts +published from it. It does not cover: + +- Third-party dependencies (report to upstream). +- LLM provider services that inspect integrates with (report to the + provider). +- Local filesystem misuse where an attacker already has shell access (out of + threat model). + +For inspect-specific threat-model notes, see the README and any docs in +this repo. diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..0ea3a94 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.2.0 diff --git a/go.mod b/go.mod index 912b3b4..c9dd6e7 100644 --- a/go.mod +++ b/go.mod @@ -2,12 +2,21 @@ module github.com/GrayCodeAI/inspect go 1.26.1 -require golang.org/x/net v0.40.0 +require ( + github.com/GrayCodeAI/hawk/sarif v0.1.0 + github.com/mark3labs/mcp-go v0.49.0 + golang.org/x/net v0.40.0 +) require ( github.com/google/jsonschema-go v0.4.2 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/mark3labs/mcp-go v0.49.0 github.com/spf13/cast v1.7.1 // indirect github.com/yosida95/uritemplate/v3 v3.0.2 // indirect ) + +// TODO(release): remove this `replace` once github.com/GrayCodeAI/hawk is +// pushed and the hawk/sarif sub-module is reachable upstream. While the +// module lives only in the local hawk-eco workspace, the replace points at +// it so `go build` works without the upstream existing. +replace github.com/GrayCodeAI/hawk/sarif => ../hawk/sarif diff --git a/internal/report/sarif.go b/internal/report/sarif.go index 257661e..a40f03d 100644 --- a/internal/report/sarif.go +++ b/internal/report/sarif.go @@ -1,108 +1,47 @@ +// SARIF 2.1.0 output for inspect, emitted via the shared +// github.com/GrayCodeAI/hawk/sarif package. + package report import ( - "encoding/json" "fmt" "net/url" -) - -// SARIF 2.1.0 output format for static analysis tool integration. -// See: https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html - -const sarifSchema = "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json" -const sarifVersion = "2.1.0" -const inspectVersion = "1.0.0" - -// SARIF top-level types - -type sarifLog struct { - Schema string `json:"$schema"` - Version string `json:"version"` - Runs []sarifRun `json:"runs"` -} - -type sarifRun struct { - Tool sarifTool `json:"tool"` - Results []sarifResult `json:"results"` -} - -type sarifTool struct { - Driver sarifDriver `json:"driver"` -} - -type sarifDriver struct { - Name string `json:"name"` - Version string `json:"version"` - InformationURI string `json:"informationUri"` - Rules []sarifRule `json:"rules"` -} - -type sarifRule struct { - ID string `json:"id"` - Name string `json:"name"` - ShortDescription sarifMessage `json:"shortDescription"` - DefaultConfig sarifDefaultConfig `json:"defaultConfiguration"` - HelpURI string `json:"helpUri,omitempty"` -} - -type sarifDefaultConfig struct { - Level string `json:"level"` -} - -type sarifMessage struct { - Text string `json:"text"` -} - -type sarifResult struct { - RuleID string `json:"ruleId"` - Level string `json:"level"` - Message sarifMessage `json:"message"` - Locations []sarifLocation `json:"locations,omitempty"` -} - -type sarifLocation struct { - PhysicalLocation sarifPhysicalLocation `json:"physicalLocation"` -} -type sarifPhysicalLocation struct { - ArtifactLocation sarifArtifactLocation `json:"artifactLocation"` - Region *sarifRegion `json:"region,omitempty"` -} + "github.com/GrayCodeAI/hawk/sarif" +) -type sarifArtifactLocation struct { - URI string `json:"uri"` -} +// ToolVersion is the inspect tool version reported in SARIF output. It is set +// at startup by the parent inspect package from the canonical VERSION file at +// the repo root. The default fallback "dev" only applies when this package is +// used directly without the parent package being initialised. +var ToolVersion = "dev" -type sarifRegion struct { - StartLine int `json:"startLine,omitempty"` - StartColumn int `json:"startColumn,omitempty"` -} +// SetToolVersion lets the parent inspect package wire its canonical Version +// into this internal package without creating an import cycle. +func SetToolVersion(v string) { ToolVersion = v } // FormatSARIF renders findings as SARIF 2.1.0 JSON. +// +// Output is delegated to the shared sarif.Builder so sight and inspect +// produce structurally identical SARIF. func FormatSARIF(data ReportData) (string, error) { - // Build rules from unique checks - ruleMap := make(map[string]int) - var rules []sarifRule + b := sarif.New(sarif.Tool{ + Name: "inspect", + Version: ToolVersion, + InformationURI: "https://github.com/GrayCodeAI/inspect", + }) for _, f := range data.Findings { - if _, exists := ruleMap[f.Check]; !exists { - ruleMap[f.Check] = len(rules) - rules = append(rules, sarifRule{ - ID: fmt.Sprintf("inspect/%s", f.Check), - Name: f.Check, - ShortDescription: sarifMessage{ - Text: fmt.Sprintf("Inspect %s check", f.Check), - }, - DefaultConfig: sarifDefaultConfig{ - Level: severityToSARIFLevel(f.Severity), - }, - }) - } - } + ruleID := fmt.Sprintf("inspect/%s", f.Check) - // Build results - var results []sarifResult - for _, f := range data.Findings { + b.AddRule(sarif.Rule{ + ID: ruleID, + Name: f.Check, + ShortDescription: fmt.Sprintf("Inspect %s check", f.Check), + Severity: severityToSARIF(f.Severity), + }) + + // Build the message with optional evidence + fix appended. msg := f.Message if f.Evidence != "" { msg += " [evidence: " + f.Evidence + "]" @@ -111,69 +50,40 @@ func FormatSARIF(data ReportData) (string, error) { msg += " [fix: " + f.Fix + "]" } - result := sarifResult{ - RuleID: fmt.Sprintf("inspect/%s", f.Check), - Level: severityToSARIFLevel(f.Severity), - Message: sarifMessage{Text: msg}, + result := sarif.Result{ + RuleID: ruleID, + Severity: severityToSARIF(f.Severity), + Message: msg, } - - // Use the URL as the artifact location if f.URL != "" { - loc := sarifLocation{ - PhysicalLocation: sarifPhysicalLocation{ - ArtifactLocation: sarifArtifactLocation{ - URI: normalizeURI(f.URL), - }, - }, - } - // If element info is available, add a region hint (line 1 as placeholder) + result.URI = normalizeURI(f.URL) + // Element is presented as a region hint; SARIF needs at least + // startLine to render a region, so we emit a placeholder line 1 + // when Element is set (keeps prior behaviour). if f.Element != "" { - loc.PhysicalLocation.Region = &sarifRegion{ - StartLine: 1, - StartColumn: 1, - } + result.Region = &sarif.Region{StartLine: 1, StartColumn: 1} } - result.Locations = []sarifLocation{loc} } - - results = append(results, result) - } - - log := sarifLog{ - Schema: sarifSchema, - Version: sarifVersion, - Runs: []sarifRun{{ - Tool: sarifTool{ - Driver: sarifDriver{ - Name: "inspect", - Version: inspectVersion, - InformationURI: "https://github.com/GrayCodeAI/inspect", - Rules: rules, - }, - }, - Results: results, - }}, + if f.Fix != "" { + result.Fix = f.Fix + } + b.AddResult(result) } - out, err := json.MarshalIndent(log, "", " ") - if err != nil { - return "", err - } - return string(out), nil + return b.String(), nil } -// severityToSARIFLevel converts internal severity to SARIF level. -// SARIF levels: "none", "note", "warning", "error" -func severityToSARIFLevel(sev Severity) string { +// severityToSARIF maps the inspect Severity enum to sarif.Severity. +func severityToSARIF(sev Severity) sarif.Severity { switch sev { case SeverityCritical, SeverityHigh: - return "error" + return sarif.SeverityError case SeverityMedium: - return "warning" + return sarif.SeverityWarning case SeverityLow: - return "note" + return sarif.SeverityNote default: - return "none" + return sarif.SeverityNone } } diff --git a/lefthook.yml b/lefthook.yml new file mode 100644 index 0000000..ba5700d --- /dev/null +++ b/lefthook.yml @@ -0,0 +1,112 @@ +# Canonical lefthook config for hawk-eco Go repos. +# Source of truth: .shared-templates/lefthook.yml.tmpl +# +# Install lefthook: +# brew install lefthook (macOS) +# go install github.com/evilmartians/lefthook@latest +# npm install -g lefthook (cross-platform) +# +# Activate hooks in this repo (one time): +# lefthook install +# +# Skip hooks for a single commit (use sparingly): +# LEFTHOOK=0 git commit -m "..." + +# --------------------------------------------------------------------------- +# pre-commit — runs before commit creation, on staged files only. +# --------------------------------------------------------------------------- +pre-commit: + parallel: true + commands: + + fmt: + glob: "*.go" + run: | + if ! command -v gofumpt >/dev/null 2>&1; then + echo "lefthook: gofumpt not installed (go install mvdan.cc/gofumpt@latest)"; exit 1 + fi + gofumpt -w {staged_files} + stage_fixed: true + + imports: + glob: "*.go" + run: | + if ! command -v goimports >/dev/null 2>&1; then + echo "lefthook: goimports not installed (go install golang.org/x/tools/cmd/goimports@latest)"; exit 1 + fi + goimports -w {staged_files} + stage_fixed: true + + lint: + glob: "*.go" + run: | + if ! command -v golangci-lint >/dev/null 2>&1; then + echo "lefthook: golangci-lint not installed — skipping (install: https://golangci-lint.run/usage/install/)" + exit 0 + fi + golangci-lint run --new-from-rev=HEAD~1 --fix {staged_files} + stage_fixed: true + + yaml-lint: + glob: "*.{yml,yaml}" + run: | + # Quick syntax check via Python's yaml module (already on most systems). + for f in {staged_files}; do + python3 -c "import sys, yaml; yaml.safe_load(open(sys.argv[1]))" "$f" || exit 1 + done + + forbidden-strings: + run: | + # Catch obvious credential-shaped strings in staged additions. + bad=$(git diff --cached --diff-filter=AM -U0 -- {staged_files} \ + | grep -E '^\+' \ + | grep -Ei '(aws_secret|password\s*=|api[_-]?key\s*=|BEGIN [A-Z]+ PRIVATE KEY)' \ + | grep -v 'example\|placeholder\|TODO\|x-release-please' || true) + if [ -n "$bad" ]; then + echo "lefthook: possible secret in staged changes:" + echo "$bad" + echo "If this is a false positive, bypass with: LEFTHOOK=0 git commit" + exit 1 + fi + +# --------------------------------------------------------------------------- +# pre-push — heavier checks, runs only on push (not every commit). +# --------------------------------------------------------------------------- +pre-push: + commands: + + test: + run: go test ./... -count=1 -timeout=60s + + vet: + run: go vet ./... + + govulncheck: + run: | + if ! command -v govulncheck >/dev/null 2>&1; then + echo "lefthook: govulncheck not installed — skipping" + exit 0 + fi + govulncheck ./... + +# --------------------------------------------------------------------------- +# commit-msg — validate Conventional Commits (release-please depends on it). +# --------------------------------------------------------------------------- +commit-msg: + commands: + + conventional-commit: + run: | + msg=$(head -n1 "{1}") + # Allow merge commits, revert commits, and release-please bot commits to bypass. + case "$msg" in + "Merge "*|"Revert "*|"chore(main): release"*) exit 0 ;; + esac + # Conventional commits regex. + if ! echo "$msg" | grep -qE '^(feat|fix|perf|refactor|test|docs|build|ci|chore|revert|style)(\([a-z0-9 _-]+\))?!?: .{1,72}$'; then + echo "lefthook: commit message does not follow Conventional Commits." + echo " format: (): " + echo " example: feat(client): add streaming retry" + echo " full guide: https://www.conventionalcommits.org/" + exit 1 + fi diff --git a/mcp/server.go b/mcp/server.go index 464d060..021bda4 100644 --- a/mcp/server.go +++ b/mcp/server.go @@ -13,7 +13,6 @@ import ( "github.com/GrayCodeAI/inspect" ) - // Server wraps the inspect library as an MCP server, exposing website // auditing capabilities to any MCP-compatible agent. type Server struct { @@ -26,7 +25,7 @@ func New(opts ...inspect.Option) *Server { s := &Server{ scanner: inspect.NewScanner(opts...), } - s.server = mcpserver.NewMCPServer("inspect", "0.1.0", + s.server = mcpserver.NewMCPServer("inspect", "0.2.0", mcpserver.WithToolCapabilities(true), ) s.registerTools() @@ -99,4 +98,3 @@ func strArg(req mcplib.CallToolRequest, key string) string { } return "" } - diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 0000000..3548ae4 --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,27 @@ +{ + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", + "packages": { + ".": { + "release-type": "go", + "package-name": "inspect", + "include-v-in-tag": true, + "include-component-in-tag": false, + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "changelog-sections": [ + { "type": "feat", "section": "Features" }, + { "type": "fix", "section": "Bug Fixes" }, + { "type": "perf", "section": "Performance" }, + { "type": "refactor", "section": "Refactoring" }, + { "type": "revert", "section": "Reverts" }, + { "type": "docs", "section": "Documentation", "hidden": false }, + { "type": "test", "section": "Tests", "hidden": false }, + { "type": "build", "section": "Build", "hidden": true }, + { "type": "ci", "section": "CI", "hidden": true }, + { "type": "chore", "section": "Chores", "hidden": true }, + { "type": "style", "section": "Style", "hidden": true } + ], + "extra-files": [{"type":"version-txt","path":"VERSION"}] + } + } +} diff --git a/version.go b/version.go new file mode 100644 index 0000000..95aab7f --- /dev/null +++ b/version.go @@ -0,0 +1,26 @@ +// Package inspect version metadata. +// +// The Version variable is sourced at compile time from the VERSION file at +// the repo root — the single source of truth used by release tooling +// (release-please, goreleaser), CI, and SARIF/JUnit/JSON output drivers. +package inspect + +import ( + _ "embed" + "strings" + + "github.com/GrayCodeAI/inspect/internal/report" +) + +//go:embed VERSION +var versionFile string + +// Version of the inspect library. Do not edit this variable directly — bump +// the VERSION file at the repo root instead. +var Version = strings.TrimSpace(versionFile) + +func init() { + // Propagate canonical version into the internal/report package so the + // SARIF tool driver field reflects the real version. + report.SetToolVersion(Version) +}