fix: harden credential and input handling

This commit is contained in:
Andy Dixon
2026-08-10 16:15:41 +01:00
parent 00ef697b03
commit ab181453f0
19 changed files with 6100 additions and 0 deletions
+223
View File
@@ -0,0 +1,223 @@
# Relata development tasks.
#
# Run `make help` for a summary.
SHELL := /bin/bash
.DEFAULT_GOAL := help
BINARY := relata
CMD_PKG := ./cmd/relata
BUILD_DIR := bin
VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo dev)
LDFLAGS := -s -w -X main.version=$(VERSION)
GO ?= go
DOCKER ?= docker
COMPOSE ?= $(DOCKER) compose
# `make setup` installs the linters into the Go bin directory, which is not
# necessarily on PATH. Look there explicitly so the tools are found either way.
GOBIN_DIR := $(shell $(GO) env GOBIN)
ifeq ($(GOBIN_DIR),)
GOBIN_DIR := $(shell $(GO) env GOPATH)/bin
endif
export PATH := $(GOBIN_DIR):$(PATH)
# Integration tests need real MongoDB and Qdrant instances, so they sit behind
# a build tag and are not run by `make test`.
INTEGRATION_TAG := integration
.PHONY: help
help: ## Show this help
@echo "Relata — development tasks"
@echo
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " \033[36m%-22s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
@echo
@echo "Typical first run:"
@echo " make setup && make compose-up && make models && make createadmin && make dev"
.PHONY: setup
setup: ## Install tooling and fetch dependencies
@echo "==> Fetching Go dependencies"
$(GO) mod download
$(GO) mod verify
@echo "==> Installing development tools"
@$(GO) install golang.org/x/vuln/cmd/govulncheck@latest 2>/dev/null || \
echo " (govulncheck unavailable — 'make security' will skip it)"
@$(GO) install honnef.co/go/tools/cmd/staticcheck@latest 2>/dev/null || \
echo " (staticcheck unavailable — 'make lint' will use go vet only)"
@if [ ! -f .env ]; then cp .env.example .env && chmod 600 .env && echo "==> Created .env from .env.example (mode 600)"; fi
@mkdir -p data/uploads $(BUILD_DIR)
@echo "==> Setup complete"
.PHONY: dev
dev: ## Run Relata from source against the compose services
@mkdir -p data/uploads
$(GO) run $(CMD_PKG) serve
.PHONY: build
build: ## Build the binary into ./bin
@mkdir -p $(BUILD_DIR)
CGO_ENABLED=0 $(GO) build -trimpath -ldflags="$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY) $(CMD_PKG)
@echo "==> Built $(BUILD_DIR)/$(BINARY) ($(VERSION))"
.PHONY: test
test: ## Run unit tests with the race detector
$(GO) test -race -count=1 ./...
.PHONY: test-short
test-short: ## Run unit tests without the race detector (faster)
$(GO) test -count=1 ./...
.PHONY: test-integration
test-integration: ## Run integration tests (needs Docker for MongoDB and Qdrant)
@echo "==> Integration tests use disposable containers; this takes a few minutes."
$(GO) test -race -count=1 -tags=$(INTEGRATION_TAG) -timeout=40m ./tests/...
# The security regression tests span both levels: the unit tests need nothing
# running, while the end-to-end ones need MongoDB and Qdrant and skip cleanly
# when those are absent.
SECURITY_PATTERN := 'Security|XSS|Injection|Traversal|Workspace|CSRF|Oversized|Malformed|Prompt|Rate|Enumerat'
.PHONY: test-security
test-security: ## Run the security regression tests (unit and end-to-end)
@echo "==> Unit-level security tests"
$(GO) test -race -count=1 -run $(SECURITY_PATTERN) ./internal/...
@echo "==> End-to-end security tests (skipped if MongoDB and Qdrant are not running)"
@# Every test in tests/security_test.go carries the TestSecurity_ prefix, so
@# this selector picks up new ones without the Makefile needing an edit.
$(GO) test -race -count=1 -tags=$(INTEGRATION_TAG) -timeout=15m -run 'TestSecurity_' ./tests/...
.PHONY: test-cover
test-cover: ## Run tests and report coverage
$(GO) test -count=1 -coverprofile=coverage.out -covermode=atomic ./...
$(GO) tool cover -func=coverage.out | tail -1
@echo "==> Full report: go tool cover -html=coverage.out"
.PHONY: fuzz
fuzz: ## Run each fuzz target briefly (parsers, model output, API input, scoring)
@# Patterns are anchored: -fuzz takes a regular expression, and an unanchored
@# name that is a prefix of another target matches both and runs neither.
$(GO) test ./internal/parsing/ -run=Fuzz -fuzz='^FuzzParseText$$' -fuzztime=30s
$(GO) test ./internal/parsing/ -run=Fuzz -fuzz='^FuzzParseJSON$$' -fuzztime=30s
$(GO) test ./internal/parsing/ -run=Fuzz -fuzz='^FuzzSniff$$' -fuzztime=30s
$(GO) test ./internal/analysis/ -run=Fuzz -fuzz='^FuzzDecodeStrict$$' -fuzztime=30s
$(GO) test ./internal/analysis/ -run=Fuzz -fuzz='^FuzzFence$$' -fuzztime=20s
$(GO) test ./internal/entities/ -run=Fuzz -fuzz='^FuzzNormalise$$' -fuzztime=20s
$(GO) test ./internal/auth/ -run=Fuzz -fuzz='^FuzzDecodeHash$$' -fuzztime=20s
$(GO) test ./internal/api/ -run=Fuzz -fuzz='^FuzzDecodeJSON$$' -fuzztime=20s
$(GO) test ./internal/api/ -run=Fuzz -fuzz='^FuzzQueryFilters$$' -fuzztime=20s
$(GO) test ./internal/search/ -run=Fuzz -fuzz='^FuzzExcerptHelpers$$' -fuzztime=20s
$(GO) test ./internal/relationships/ -run=Fuzz -fuzz='^FuzzExcerpt$$' -fuzztime=20s
$(GO) test ./internal/relationships/ -run=Fuzz -fuzz='^FuzzScore$$' -fuzztime=20s
$(GO) test ./internal/entities/ -run=Fuzz -fuzz='^FuzzExtractDeterministic$$' -fuzztime=20s
$(GO) test ./internal/entities/ -run=Fuzz -fuzz='^FuzzExtractDeterministicClaims$$' -fuzztime=20s
$(GO) test ./internal/ingest/ -run=Fuzz -fuzz='^FuzzSanitiseFilename$$' -fuzztime=20s
.PHONY: lint
lint: fmt-check ## Run go vet and staticcheck
@echo "==> go vet"
$(GO) vet ./...
@if command -v staticcheck >/dev/null 2>&1; then \
echo "==> staticcheck"; staticcheck ./...; \
else \
echo "==> staticcheck not installed; run 'make setup' to add it"; \
fi
.PHONY: fmt
fmt: ## Format the code
$(GO) fmt ./...
@command -v gofmt >/dev/null && gofmt -s -w .
.PHONY: fmt-check
fmt-check: ## Fail if any file is not gofmt-clean
@unformatted=$$(gofmt -s -l . 2>/dev/null | grep -v '^$$' || true); \
if [ -n "$$unformatted" ]; then \
echo "These files are not formatted (run 'make fmt'):"; echo "$$unformatted"; exit 1; \
fi
.PHONY: security
security: ## Run vulnerability and static security checks
@echo "==> Dependency vulnerability scan"
@if command -v govulncheck >/dev/null 2>&1; then \
govulncheck ./...; \
else \
echo " govulncheck not installed; run 'make setup'"; \
fi
@echo "==> Security regression tests"
@$(MAKE) --no-print-directory test-security
@echo "==> Checking that no secrets are committed"
@if git ls-files --error-unmatch .env >/dev/null 2>&1; then \
echo " .env is tracked by git — it must not be committed"; exit 1; \
else echo " .env is not tracked"; fi
.PHONY: compose-up
compose-up: ## Start MongoDB, Qdrant and Ollama
$(COMPOSE) up -d mongodb qdrant ollama
@echo "==> Waiting for services to report healthy…"
@for i in $$(seq 1 60); do \
if $(COMPOSE) ps --format json 2>/dev/null | grep -q '"Health":"starting"'; then sleep 2; else break; fi; \
done
@$(COMPOSE) ps
@echo
@echo "Next: make models (pulls the Ollama models — a few GB, once)"
.PHONY: compose-down
compose-down: ## Stop the services, keeping data
$(COMPOSE) down
.PHONY: compose-logs
compose-logs: ## Follow the service logs
$(COMPOSE) logs -f
.PHONY: models
models: ## Pull the Ollama models Relata uses
$(COMPOSE) --profile setup up ollama-pull
.PHONY: createadmin
createadmin: ## Create the initial administrator account
$(GO) run $(CMD_PKG) createadmin --username $${RELATA_ADMIN_USERNAME:-admin}
.PHONY: seed
seed: ## Load the demonstration dataset
$(GO) run $(CMD_PKG) seed
.PHONY: seed-wait
seed-wait: ## Load the demonstration dataset and wait for processing
$(GO) run $(CMD_PKG) seed --wait
.PHONY: docker-build
docker-build: ## Build the production container image
$(DOCKER) build --build-arg VERSION=$(VERSION) -t relata:$(VERSION) -t relata:latest .
.PHONY: docker-run
docker-run: ## Run Relata in a container against the compose services
$(COMPOSE) --profile app up -d --build app
@echo "==> Relata is at http://127.0.0.1:8080"
.PHONY: openapi
openapi: ## Verify the OpenAPI specification against the implemented routes
@echo "==> Checking docs/openapi.yaml against the registered routes"
$(GO) test ./internal/api/ -run OpenAPI -count=1
@echo "==> docs/openapi.yaml matches the implementation"
.PHONY: backup
backup: ## Back up MongoDB and the uploaded originals
./scripts/backup.sh
.PHONY: reset
reset: ## Delete all local data and start again (destructive)
@echo "This deletes every stored item, vector and account."
@read -p "Type 'reset' to confirm: " ans && [ "$$ans" = "reset" ] || (echo "Cancelled."; exit 1)
$(COMPOSE) down -v
rm -rf data/uploads/*
@echo "==> Local data removed. Run 'make compose-up && make createadmin' to start again."
.PHONY: clean
clean: ## Remove build artefacts
rm -rf $(BUILD_DIR) coverage.out
$(GO) clean -testcache
@echo "==> Cleaned"
.PHONY: check
check: fmt-check lint test ## Everything CI runs on a pull request
@echo "==> All checks passed"