Generate GNU Make build systems that define build targets, configure dependencies, set up phony targets, and implement parallel builds. Use when creating make/Makefile/.mk files, implementing compile rules, or building production-ready build automation for C/C++, Go, Python, and Java projects.
93
Does it follow best practices?
Validation for skill structure
This guide covers security best practices for Makefiles, including secrets management, input validation, shell injection prevention, and CI/CD security considerations.
DO NOT hardcode credentials, API keys, or passwords in Makefiles:
# WRONG: Hardcoded credentials
DB_PASSWORD := mysecretpassword
AWS_SECRET := AKIAIOSFODNN7EXAMPLEPass secrets via environment variables:
# CORRECT: Environment variable with validation
DB_PASSWORD ?= $(error DB_PASSWORD is not set)
AWS_SECRET_KEY ?= $(error AWS_SECRET_KEY is not set)
deploy:
@echo "Deploying with credentials from environment..."
./deploy.sh# Include .env file if it exists (never commit .env!)
-include .env
export
# Ensure .gitignore contains .env
.PHONY: check-env
check-env:
@grep -q '^\.env$$' .gitignore || echo "WARNING: Add .env to .gitignore!"AWS Secrets Manager:
# Fetch secret at runtime, don't cache in Makefile variables
deploy:
@DB_PASSWORD=$$(aws secretsmanager get-secret-value \
--secret-id prod/db/password \
--query SecretString --output text) && \
./deploy.shHashiCorp Vault:
deploy:
@DB_PASSWORD=$$(vault kv get -field=password secret/database) && \
./deploy.shAlways validate user-provided variables:
# Validate PROJECT_NAME contains only safe characters
PROJECT_NAME := $(strip $(PROJECT_NAME))
ifneq ($(PROJECT_NAME),$(shell echo '$(PROJECT_NAME)' | tr -cd 'a-zA-Z0-9_-'))
$(error PROJECT_NAME contains invalid characters. Use only [a-zA-Z0-9_-])
endif# WRONG: Unquoted variables - vulnerable to injection
process:
./script.sh $(USER_INPUT)
# CORRECT: Quoted variables
process:
./script.sh '$(USER_INPUT)'# WRONG: Shell will interpret special characters
echo-input:
@echo $(MESSAGE)
# SAFER: Use printf with proper quoting
echo-input:
@printf '%s\n' '$(MESSAGE)'# NEVER do this - allows arbitrary command execution
run-command:
$(USER_COMMAND)
# NEVER pipe untrusted input to shell
execute:
echo $(INPUT) | sh
# NEVER use eval with user input
eval-input:
@eval $(USER_INPUT)# Use := for values that shouldn't be re-evaluated
SAFE_VALUE := $(shell whoami)
# = causes re-evaluation each time - potential for injection
# if EXTERNAL_VAR changes after assignment
UNSAFE_VALUE = $(EXTERNAL_VAR)When working with passwords containing $:
# Password with $ sign - double the $ to escape
# If password is "pa$$word", set it as:
PASSWORD := pa$$$$word
# Or read from file where $ is already escaped
PASSWORD := $(shell cat .password | sed 's/\$$/\$\$\$\$/g')# WRONG: User can specify "../../../etc/passwd"
read-file:
cat $(FILE_PATH)
# SAFER: Validate path is within expected directory
SAFE_DIR := ./data
read-file:
@case "$(FILE_PATH)" in \
$(SAFE_DIR)/*) cat "$(FILE_PATH)" ;; \
*) echo "ERROR: Invalid path" >&2; exit 1 ;; \
esac# Use mktemp for secure temporary files
process:
@TMPFILE=$$(mktemp) && \
trap 'rm -f "$$TMPFILE"' EXIT && \
./generate-config > "$$TMPFILE" && \
./process-config "$$TMPFILE"# Set restrictive permissions on sensitive files
install-config:
install -m 600 config.secret $(DESTDIR)/etc/myapp/
# Create directories with appropriate permissions
install-dirs:
install -d -m 700 $(DESTDIR)/var/lib/myapp/secrets# WRONG: Password visible in logs
deploy:
curl -u user:$(PASSWORD) https://api.example.com
# CORRECT: Suppress command echo
deploy:
@curl -u user:$(PASSWORD) https://api.example.com
# BEST: Use credential helper
deploy:
@curl --netrc-file ~/.netrc https://api.example.com# Use strict mode
SHELL := bash
.SHELLFLAGS := -eu -o pipefail -c
# Ensure sensitive operations fail closed
deploy:
@test -n "$(API_KEY)" || { echo "ERROR: API_KEY not set" >&2; exit 1; }
@./deploy.sh# Don't inherit all environment variables
# Only export what's needed
unexport HISTFILE
unexport AWS_SESSION_TOKEN
# Explicitly export required variables
export PATH
export HOMEAUDIT_LOG := /var/log/makefile-audit.log
audit-log = @echo "$$(date -Iseconds) [$(1)] $(2)" >> $(AUDIT_LOG)
deploy: check-permissions
$(call audit-log,DEPLOY,Starting deployment by $$USER)
./deploy.sh
$(call audit-log,DEPLOY,Deployment completed)# Always verify downloads
CHECKSUM_FILE := checksums.sha256
download:
curl -fsSL -o package.tar.gz https://example.com/package.tar.gz
sha256sum -c $(CHECKSUM_FILE)
# Or use GPG verification
download-verified:
curl -fsSL -o package.tar.gz https://example.com/package.tar.gz
curl -fsSL -o package.tar.gz.asc https://example.com/package.tar.gz.asc
gpg --verify package.tar.gz.asc package.tar.gz# Force HTTPS for all downloads
CURL_OPTS := --proto '=https' --tlsv1.2
download:
curl $(CURL_OPTS) -fsSL -o file.txt https://example.com/file.txtdocker-build:
docker build --build-arg USER_ID=$$(id -u) --build-arg GROUP_ID=$$(id -g) -t myapp .
# In Dockerfile, create non-root userIMAGE := myapp:latest
.PHONY: docker-scan
docker-scan: docker-build
@if command -v trivy >/dev/null 2>&1; then \
trivy image --exit-code 1 --severity HIGH,CRITICAL $(IMAGE); \
else \
echo "WARNING: trivy not found, skipping security scan"; \
fi# WRONG: Secret visible in image layers
docker-build:
docker build --build-arg API_KEY=$(API_KEY) -t myapp .
# CORRECT: Use build secrets (BuildKit)
docker-build:
DOCKER_BUILDKIT=1 docker build \
--secret id=api_key,src=.api_key \
-t myapp .# Secure and strict Makefile configuration
SHELL := bash
.SHELLFLAGS := -eu -o pipefail -c
.DELETE_ON_ERROR:
MAKEFLAGS += --warn-undefined-variables
MAKEFLAGS += --no-builtin-rules
# Prevent accidental exposure
unexport HISTFILE# Prevent running all targets by accident
.PHONY: all
all:
@echo "Please specify a target. Run 'make help' for options."
@exit 1Before committing a Makefile:
.env file listed in .gitignoreeval with external input@ to hide from logsInstall with Tessl CLI
npx tessl i pantheon-ai/makefile-generator