-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathMakefile
More file actions
232 lines (197 loc) · 14.6 KB
/
Copy pathMakefile
File metadata and controls
232 lines (197 loc) · 14.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# Makefile — minimal entry points for maintainer workflows.
# This is dev tooling, not a build system. The plugin itself ships no binaries.
#
# Run `make help` for an overview.
.DEFAULT_GOAL := help
# Use .venv if present, otherwise fall back to system python3
PYTHON ?= $(if $(wildcard .venv/bin/python3),.venv/bin/python3,python3)
# Absolute path to this Makefile's directory (the plugin root), stable even
# when make is invoked from a different working directory.
PLUGIN_ROOT := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
# ─────────────────────────────────────────────────────────────────────────────
# Manual E2E full-run
#
# Triggered ONLY manually — after a non-trivial refactor or before a release.
# Never wired into PR / push / cron hooks. See README "Manual full-run check".
#
# Auth: subscription (default, via `claude /login`) or ANTHROPIC_API_KEY.
# Cost: ~30–50% of a Pro 5h-window OR ~$0.30–1.00 with haiku-tier API usage.
# Time: ~10–15 min for `quick` depth on the bundled synthetic-repo fixture.
# ─────────────────────────────────────────────────────────────────────────────
.PHONY: e2e-full
e2e-full: ## Run the full E2E pipeline + assertions against the synthetic-repo fixture
@command -v claude >/dev/null 2>&1 || { \
echo "ERROR: 'claude' CLI not on PATH. Install Claude Code first."; exit 3; }
@./tests/e2e/run-full.sh --depth quick
.PHONY: e2e-full-standard
e2e-full-standard: ## Standard depth with Stage-3 QA enabled (slower, higher fidelity)
@command -v claude >/dev/null 2>&1 || { \
echo "ERROR: 'claude' CLI not on PATH. Install Claude Code first."; exit 3; }
@./tests/e2e/run-full.sh --depth standard
.PHONY: e2e-full-thorough
e2e-full-thorough: ## Thorough depth with Stage-3 QA and Stage-4 architect review
@command -v claude >/dev/null 2>&1 || { \
echo "ERROR: 'claude' CLI not on PATH. Install Claude Code first."; exit 3; }
@./tests/e2e/run-full.sh --depth thorough
.PHONY: e2e-full-repair
e2e-full-repair: e2e-full-standard ## Build a clean standard seed, corrupt §7, and verify the live Re-Render Loop
@command -v claude >/dev/null 2>&1 || { \
echo "ERROR: 'claude' CLI not on PATH. Install Claude Code first."; exit 3; }
@./tests/e2e/run-repair.sh
.PHONY: e2e-full-eval
e2e-full-eval: e2e-full ## Run the adversarial semantic-quality judge over a fresh full E2E result
@./tests/e2e/run-eval.sh
.PHONY: e2e-fixture-suite
e2e-fixture-suite: ## Run all six external language/architecture fixtures and their recall oracles
@./tests/e2e/run-fixture-suite.sh
.PHONY: ci-triage
ci-triage: ## Fetch a dispatched run's artifacts (fixture E2E or threat model) and summarise failures: make ci-triage RUN_ID=<id> [INTO=.appsec-ci]
@test -n "$(RUN_ID)" || { echo "ERROR: set RUN_ID=<github run id>. List runs with: gh run list --workflow fixture-e2e-dispatch.yml -L 10"; exit 2; }
@./scripts/ci_triage.sh --run-id "$(RUN_ID)" --into "$(or $(INTO),.appsec-ci)"
.PHONY: e2e-full-keep
e2e-full-keep: ## Re-run assertions against the previous _last-run/ output (no pipeline re-run)
@APPSEC_E2E_FULL=1 \
APPSEC_E2E_OUTPUT_DIR="$(PWD)/tests/fixtures/e2e/_last-run" \
$(PYTHON) -m pytest tests/test_full_run_e2e.py -v --tb=short
.PHONY: analyze-verify
analyze-verify: ## Full pipeline + structural assertions against ANY repo (recall/oracle checks self-skip): make analyze-verify REPO=<path> [DEPTH=quick|standard|thorough]
@command -v claude >/dev/null 2>&1 || { \
echo "ERROR: 'claude' CLI not on PATH. Install Claude Code first."; exit 3; }
@test -n "$(REPO)" || { echo "ERROR: set REPO=<path to repo to verify>, e.g. make analyze-verify REPO=/home/mrohr/juice-shop"; exit 2; }
@./tests/e2e/run-full.sh --repo "$(REPO)" --depth "$(or $(DEPTH),quick)"
# ─────────────────────────────────────────────────────────────────────────────
# Fast unit tests (the per-PR safety net — runs in CI too)
# ─────────────────────────────────────────────────────────────────────────────
.PHONY: test
test: ## Run the standard pytest suite with coverage (no LLM)
@$(PYTHON) -m pytest tests/ -v --tb=short --cov=scripts --cov-report=term-missing; \
status=$$?; rm -rf .coverage-data; exit $$status
.PHONY: coverage
coverage: ## Run the suite + write an HTML coverage report to htmlcov/index.html
@$(PYTHON) -m pytest tests/ --tb=short --cov=scripts --cov-report=term-missing --cov-report=html; \
status=$$?; rm -rf .coverage-data; exit $$status
@echo "HTML report: htmlcov/index.html"
.PHONY: test-incremental
test-incremental: ## Fast focused subset: incremental-scan reconciliation + 2-run main() E2E (no LLM)
@$(PYTHON) -m pytest tests/test_incremental_two_run_e2e.py tests/test_build_threat_model_yaml.py -q
.PHONY: lint
lint: ## Ruff check + format check
@ruff check scripts/ tests/ hooks/
@ruff format --check scripts/ tests/ hooks/
.PHONY: fix
fix: ## Auto-repair the mechanical gate failures (ruff lint + format), then list what still needs a human
@echo ">> ruff check --fix (safe lint fixes)"; ruff check --fix scripts/ tests/ hooks/ || true
@echo ">> ruff format"; ruff format scripts/ tests/ hooks/
@echo ""
@echo "Auto-repair done. Stages 3-6 are NOT auto-fixable by design (fix the producer, not the symptom):"
@echo " - validate_config.py -> correct the offending config field"
@echo " - check_fragment_registry -> align the registry maps (docs/internal/runbooks/adding-a-section.md)"
@echo " - check_target_specificity -> make the rule generic, or move the target name into a comment"
@echo " - pytest / coverage -> separate pre-existing from new failures; add tests, don't lower the floor"
@echo " - check_release_meta.py -> reconcile pyproject version / git tag / CHANGELOG heading"
@echo ""
@echo "Re-run 'make check' (or 'make release-check') to see what remains."
# ─────────────────────────────────────────────────────────────────────────────
# Vendored content
#
# `baseline-sync` re-vendors data/baselines/ from the published baseline. It
# fetches over the network, so it is a maintainer command and never part of a
# gate: `check` and `release-check` stay deterministic and offline, and a
# release must not fail because a host is down.
# ─────────────────────────────────────────────────────────────────────────────
.PHONY: baseline-sync
baseline-sync: ## Re-vendor data/baselines/ from the published baseline: make baseline-sync [DRY=1] [ACCEPT_ID=aisec-0.2]
@$(PYTHON) scripts/sync_baseline.py \
$(if $(DRY),--dry-run,) \
$(if $(ACCEPT_ID),--accept-id "$(ACCEPT_ID)",)
# ─────────────────────────────────────────────────────────────────────────────
# Release gates
#
# `check` — the continuous gate. Runs on every dev/main push & PR in CI.
# Must be green on EVERY commit (code health + drift guards).
# `release-check` — superset, for the release boundary only. Adds version/tag/
# changelog hygiene. Run it when PREPARING a release (version
# bumped + CHANGELOG entry written), not on routine dev commits.
# ─────────────────────────────────────────────────────────────────────────────
.PHONY: check
check: ## Continuous gate: lint, format, config, drift, full test suite (no coverage)
@ruff check scripts/ tests/ hooks/
@ruff format --check scripts/ tests/ hooks/
@python3 scripts/validate_config.py .
@python3 scripts/check_fragment_registry.py
@python3 scripts/check_target_specificity.py
@python3 scripts/check_specs.py
@# Run WITHOUT --cov: coverage enables `[tool.coverage.run] patch=["subprocess"]`,
@# which instruments every child interpreter. The subprocess-heavy integration
@# tests (e.g. test_incremental_mode spawning run-headless.sh) then crawl and the
@# release gate appears to hang. Coverage is enforced separately by `make test` /
@# `make coverage` (and the CI coverage job), not on this fast correctness gate.
@$(PYTHON) -m pytest tests/ --tb=short
.PHONY: release-check
release-check: ## Release-boundary gate: `check` + version/tag/changelog consistency
@mkdir -p .cache
@bash scripts/run-interruptible.sh .cache/release-check.log \
bash -c '$(MAKE) --no-print-directory check && python3 scripts/check_release_meta.py'
.PHONY: release-all
release-all: ## Full pre-release sequence: release-check then a live e2e-full (quick)
@$(MAKE) --no-print-directory release-check
@$(MAKE) --no-print-directory e2e-full
# ─────────────────────────────────────────────────────────────────────────────
# Target-repo setup — write required Claude Code permissions into a target repo
# ─────────────────────────────────────────────────────────────────────────────
.PHONY: setup-target
setup-target: ## Write required CC permissions into a target repo (default: cwd): make setup-target [REPO=<path>] [OUTPUT=<path>] [SCOPE=local|project|user]
@mkdir -p "$(or $(REPO),$(CURDIR))/.claude"
@$(PYTHON) $(PLUGIN_ROOT)scripts/check_permissions.py \
--repo-root "$(or $(REPO),$(CURDIR))" \
$(if $(OUTPUT),--output-dir "$(OUTPUT)",) \
--plugin-dir "$(PLUGIN_ROOT)" \
--scope "$(or $(SCOPE),local)" \
--update
@echo "Restart Claude Code (or reload the session) for the new permissions to take effect."
# ─────────────────────────────────────────────────────────────────────────────
# Diagnostic bundle — anonymised user→maintainer error report
#
# Build a finding-free .tgz from a failed run's OUTPUT_DIR (versions, run shape,
# metadata-only inventory, scrubbed logs) and inspect one on the analysis side.
# Never contains threat-model results, evidence, or source. See CONTRIBUTING.md.
# ─────────────────────────────────────────────────────────────────────────────
.PHONY: diagnostic-bundle
diagnostic-bundle: ## Build an anonymised diagnostic .tgz from a run: make diagnostic-bundle RUN=<repo>/docs/security [REPO_ROOT=<repo>] [INTO=.]
@test -n "$(RUN)" || { echo "ERROR: set RUN=<run OUTPUT_DIR>, e.g. make diagnostic-bundle RUN=<repo>/docs/security"; exit 2; }
@$(PYTHON) scripts/diagnostic_bundle.py collect --run "$(RUN)" --into "$(or $(INTO),.)" $(if $(REPO_ROOT),--repo-root "$(REPO_ROOT)",)
.PHONY: inspect-bundle
inspect-bundle: ## Print a triage summary of a diagnostic bundle: make inspect-bundle BUNDLE=appsec-diag-<id>.tgz
@test -n "$(BUNDLE)" || { echo "ERROR: set BUNDLE=<path to .tgz or unpacked dir>"; exit 2; }
@$(PYTHON) scripts/diagnostic_bundle.py inspect --bundle "$(BUNDLE)"
# ─────────────────────────────────────────────────────────────────────────────
# Ad-hoc headless analysis against an arbitrary target repo
#
# Thin wrapper around scripts/run-headless.sh for maintainer spot-checks
# (e.g. juice-shop). Streams to the terminal AND a log file by default; set
# BG=1 to detach via nohup so this session is free to work in parallel.
# ─────────────────────────────────────────────────────────────────────────────
.PHONY: analyze
analyze: ## Headless threat-model against any repo: make analyze REPO=<path> [BG=1] [LOG=<file>] [MAX_DURATION=9000] [EXTRA="--assessment-depth thorough"]
@test -n "$(REPO)" || { echo "ERROR: set REPO=<path to repo to analyze>, e.g. make analyze REPO=/home/mrohr/juice-shop"; exit 2; }
@log="$(or $(LOG),$(HOME)/appsec-$(notdir $(patsubst %/,%,$(REPO))).log)"; \
cmd='APPSEC_PLUGIN_DEV=1 $(PLUGIN_ROOT)scripts/run-headless.sh --repo "$(REPO)" --verbose --max-duration $(or $(MAX_DURATION),9000) $(EXTRA)'; \
if [ -n "$(BG)" ]; then \
nohup sh -c "$$cmd" >"$$log" 2>&1 & \
echo "▶ analyzing $(REPO) in background (PID $$!)"; \
echo " log: $$log"; \
echo " tail: tail -f $$log"; \
else \
echo "▶ analyzing $(REPO) (log → $$log)"; \
sh -c "$$cmd" 2>&1 | tee "$$log"; \
fi
.PHONY: analyze-resume
analyze-resume: ## Resume an interrupted analyze from its checkpoint — reuses .stride-*.json (no re-scan). Refuses if a run still holds the lock; kill it first, then: make analyze-resume REPO=<path> [BG=1] [LOG=<file>]
@test -n "$(REPO)" || { echo "ERROR: set REPO=<path to repo to resume>, e.g. make analyze-resume REPO=/home/mrohr/juice-shop"; exit 2; }
@$(MAKE) --no-print-directory analyze REPO="$(REPO)" BG="$(BG)" LOG="$(LOG)" MAX_DURATION="$(MAX_DURATION)" EXTRA="--resume $(EXTRA)"
# ─────────────────────────────────────────────────────────────────────────────
# Help
# ─────────────────────────────────────────────────────────────────────────────
.PHONY: help
help: ## Show this help
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z0-9_-]+:.*?## / {printf " \033[36m%-22s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)