From 91fab0c99691ed42a4ef34ecafbe8df2b189a0a6 Mon Sep 17 00:00:00 2001 From: Patrick Lewis Date: Wed, 14 Oct 2026 13:29:57 -0700 Subject: [PATCH] chore: tighten agent environment and test suite - stub sleep in bats tests (suite ~44s -> ~14s) - add tests/test_repo_structure.bats enforcing the new-script checklist - add preToolUse hook blocking heredoc file writes (with bats tests) - move per-script behaviors to docs/script-behaviors.md; slim AGENTS.md - prune instruction files irrelevant to a bash-only repo - document mkdir-before-create step for new scripts Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/copilot-instructions.md | 82 +- .github/hooks/block-file-writes-via-bash.json | 14 + .../scripts/block-file-writes-via-bash.sh | 22 + ...tion-docker-best-practices.instructions.md | 681 ----------- .../context-engineering.instructions.md | 44 - .../devops-core-principles.instructions.md | 167 --- .github/instructions/makefile.instructions.md | 410 ------- ...engineering-best-practices.instructions.md | 174 --- .../security-and-owasp.instructions.md | 1057 ----------------- AGENTS.md | 31 +- docs/script-behaviors.md | 77 ++ tests/test_common.bats | 4 + tests/test_hooks.bats | 59 + tests/test_repo_structure.bats | 69 ++ tests/test_script_validation.bats | 3 + 15 files changed, 257 insertions(+), 2637 deletions(-) create mode 100644 .github/hooks/block-file-writes-via-bash.json create mode 100755 .github/hooks/scripts/block-file-writes-via-bash.sh delete mode 100644 .github/instructions/containerization-docker-best-practices.instructions.md delete mode 100644 .github/instructions/context-engineering.instructions.md delete mode 100644 .github/instructions/devops-core-principles.instructions.md delete mode 100644 .github/instructions/makefile.instructions.md delete mode 100644 .github/instructions/qa-engineering-best-practices.instructions.md delete mode 100644 .github/instructions/security-and-owasp.instructions.md create mode 100644 docs/script-behaviors.md create mode 100644 tests/test_hooks.bats create mode 100644 tests/test_repo_structure.bats diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 39099f7..294728b 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -145,87 +145,13 @@ Scripts follow this validation sequence: ## Script-Specific Behaviors -### `github-add-enterprise-team-read-permissions` - -Uses GraphQL cursor-based pagination to enumerate all enterprise orgs, then assigns the `all_repo_read` org role (configurable via `ALL_REPO_READ_ROLE_NAME`) to the enterprise team in each org. Requires Bearer token authentication with `admin:enterprise` scope. The `ENTERPRISE_TEAM_SLUG` is the slug without the `"ent:"` prefix. - -### `github-add-repo-collaborators-by-pattern` - -Adds individual collaborators to org repos whose names match `REPO_NAME_REGEX` (ERE). Accepts a comma-separated `COLLABORATORS` list and an optional `REPO_EXCLUDE_REGEX` to skip matching repos. Uses `token` auth for all REST calls. Iterates all org repos paginated at 100 per page. - -### `github-add-repo-permissions` - -Grants team permissions across all repos in an org. Reads five space-separated team-slug lists (`REPO_ADMIN`, `REPO_MAINTAIN`, `REPO_PUSH`, `REPO_TRIAGE`, `REPO_PULL`). Optionally filters repos by name prefix via `REPO_NAME_FILTER`. Sleeps 5 s between each repo to stay within rate limits. Uses legacy `Accept: application/vnd.github.v3+json` and `token` auth. - -### `github-archive-old-repos` - -Calculates a cutoff date based on `YEARS_THRESHOLD` (default 5). Generates a timestamped CSV report under `reports/`, shows the top 10 oldest repos, and prompts for confirmation before archiving. Handles both BSD (`date -v`, macOS) and GNU (`date -d`, Linux) date syntax for cross-platform compatibility. - -### `github-auto-repo-creation` - -Creates private repos from a comma-separated `REPO_NAMES` list. Configures branch protection on the default branch, creates a CODEOWNERS file (Base64-encoded via API), and grants admin access to comma-separated `ADMIN_TEAMS`. All slug values in `REPO_NAMES`, `ADMIN_TEAMS`, and `REPO_OWNERS` are validated before use. Requires `base64` in addition to `curl` and `jq`. - -### `github-close-archived-repo-security-alerts` - -Dismisses open Dependabot, code-scanning, and secret-scanning alerts on all archived repos. Supports `--type` to target a single alert type and `--dry-run` to preview without changes. Generates a timestamped CSV report under `reports/`. Requires a token with `security_events` and `repo` scopes. - -### `github-dockerfile-discovery` - -Uses GitHub code-search API to find Dockerfiles across all enterprise orgs, then fetches and parses each file to extract `FROM` instructions (including multi-stage builds). Produces three timestamped reports in `REPORT_DIR`: a detail CSV, a summary CSV, and a plain-text summary. Supports `ORGS` override, `ORG_FILTER`/`ORG_EXCLUDE` regex filters (validated as syntactically correct ERE before use), and configurable sleep intervals (`SEARCH_SLEEP`, `CONTENT_SLEEP`). Both `gh_api` call sites handle sentinels: `__422__` or `__404__` on the code-search endpoint skips that org with a warning; `__404__` or `__422__` on the file-contents endpoint skips that individual file. - -### `github-enable-issues` - -Iterates all non-archived repos in an org and enables the Issues feature on any repo where it is disabled. Supports `--dry-run` to list affected repos without making changes. - -### `github-get-consumed-licenses` - -Calls the enterprise consumed-licenses endpoint using Bearer authentication. Requires `read:enterprise` scope. Returns seat consumption and purchase counts. Token-only script; no pagination or retry logic beyond what the single API call provides. - -### `github-get-public-repos` - -Discovers all enterprise orgs (three-tier fallback via `get_enterprise_orgs`), fetches all repos in each, and filters to public visibility in `jq` (does not rely on `?type=public` query param, which is unreliable for enterprise-managed orgs). Writes a timestamped CSV to `REPORT_DIR`. Supports `ORGS` override and `ORG_FILTER`/`ORG_EXCLUDE` ERE regex filters. - -### `github-get-repo-list` - -Outputs a CSV row per repository (full name, owner, visibility, URL, description, fork flag, pushed/created/updated timestamps) to stdout. Pagination handled at 100 repos per page via `get_repo_page_count`. - -### `github-import-repo` - -Performs a full bare clone (`git clone --mirror`) of a source repo and pushes all branches, tags, and history to a new private destination repo. Validates `GIT_URL_PREFIX` against a GitHub-host allowlist before running any git operations to prevent credential leakage. Repo names and `OWNER_USERNAME` are validated as slugs. - -### `github-migrate-internal-repos-to-private` - -Fetches all repos with internal visibility (paginated) and converts each to private via PATCH. Logs success or failure per repo. This operation cannot be undone via the API — converting internal to private removes access from members of other enterprise orgs. - -### `github-monthly-issues-report` - -Generates an HTML report of issues created in a date range (`MONTH_START`/`MONTH_END`), filtered by a hardcoded label (`Linked [AC]` — edit the script to change). Uses the timeline API to track who applied labels. URL-encodes labels for API calls (e.g., `Linked [AC]` → `Linked%20[AC]`). Outputs to `output.txt`. - -### `github-organize-stars` - -Uses `gh` CLI GraphQL (not `curl`) to fetch all starred repos. Accepts `GITHUB_TOKEN` env var or an active `gh` auth session for authentication. Categorizes by primary language, GitHub topics, and name keywords using a `RULES` array (pipe-delimited: `List Name|LANGUAGES|TOPICS|NAME_KEYWORDS`; first matching rule wins). Caches stars at `~/.cache/gh-star-organizer/stars.json`. Supports `--dry-run`, `-y` (skip confirm), `--show-repos`, and `--no-cache`. Adds repos to Lists in batches of 25. - -### `github-repo-from-template` - -Creates a private repo from `TEMPLATE_REPO` (including all branches), assigns admin permissions to space-separated `REPO_ADMIN` teams, write permissions to `REPO_WRITE` teams, then invites `CD_USERNAME` as a collaborator using `CD_GITHUB_TOKEN` and auto-accepts the invitation. All slug values are validated before use. - -### `github-repo-permissions-report` - -Uses `curl` for all API calls. Accepts `GITHUB_TOKEN` env var or an active `gh` auth session (token auto-resolved at lib source time) for authentication. Accepts `-r OWNER/REPO`, `-b BRANCH`, and `-o FILE` flags. Outputs a CSV with two record types: `permission` (all users/teams) and `bypass_actor` (explicit PR approval bypass entries). Reports both branch protection rules and repository rulesets. - -### `github-copilot-report` - -Uses `curl` for all GitHub API calls. Accepts `GITHUB_TOKEN` env var (PAT with `read:enterprise` and `manage_billing:enterprise` scopes) or an active `gh` auth session (token auto-resolved at lib source time) for authentication. Also requires `az` to be **installed** when Entra ID enrichment is needed — `az` is checked at runtime only if `--no-entra` is not set and `az` is not already skipped. If `az` is not installed or not logged in, Entra lookup is silently skipped. When `az` is logged in, enriches each user with Entra ID department and job title via `az rest`. - -Auto-detects credits per seat from a promo/standard table keyed on plan type and today's date (promo period Jun 1 – Sep 1, 2026); override with `--credits N` or `$CREDITS_PER_SEAT_OVERRIDE`. Credits are pooled enterprise-wide, not per-user buckets. Code completions are not billed in AI credits. - -Uses API version `2026-03-10` and the new usage-metrics NDJSON endpoints (signed download links). The legacy `/copilot/metrics` and `/copilot/usage` endpoints were closed Apr 2, 2026. +Per-script quirks (auth style, pagination, sentinels, reports) live in [`docs/script-behaviors.md`](../docs/script-behaviors.md). Read the matching section before changing an existing script. ## Development Guidelines ### Adding New Scripts -1. Create directory: `github-/` -2. Create script: `github-.sh` (match directory name) +1. Create directory `/github-/` with `mkdir -p` in its own bash call, before any `create` call (the `create` tool needs an existing parent) +2. Create script: `github-.sh` (match directory name); add a section to `docs/script-behaviors.md`. `tests/test_repo_structure.bats` fails until every checklist item below exists 3. Source `lib/github-common.sh` for validation and output helpers 4. Start with the standard boilerplate (see Script Anatomy above) 5. Create `action.yml` in the same directory — expose every env var as a named input (required inputs without defaults, optional inputs with sensible defaults); map CLI flags such as `--dry-run` and `--type` to boolean/string inputs and build an `ARGS` array in the `run:` step. Mirror the pattern of any existing `action.yml`. @@ -305,7 +231,7 @@ When you change one of these files, you must also update the files in the "Also | `README.md` — script documentation | Verify the script's `# ===` header comment still matches (env vars, options, requirements) | | `.githooks/pre-commit` | `install-hooks.sh` if hook path or installation instructions change; README.md Best Practices section | | `install-hooks.sh` | README.md Installation section | -| Add a new script | `tests/test_script_validation.bats` (add a test section for the new script); `action.yml` in the same directory; `README.md` (add use case, env var table, usage example, Available Actions table row) | +| Add a new script | `tests/test_script_validation.bats`; `action.yml` in the same directory; `README.md` (use case, env var table, usage example, Available Actions row); `docs/script-behaviors.md`. `tests/test_repo_structure.bats` enforces the first four | | Add a new domain folder | `README.md` top-level structure description; `AGENTS.md` Repository Structure section | | `.github/workflows/ci.yml` — shellcheck flags | `.githooks/pre-commit` shellcheck invocation (keep them in sync) | | `.github/workflows/copilot-setup-steps.yml` — tool versions | `AGENTS.md` Tech Stack table | diff --git a/.github/hooks/block-file-writes-via-bash.json b/.github/hooks/block-file-writes-via-bash.json new file mode 100644 index 0000000..21e4b50 --- /dev/null +++ b/.github/hooks/block-file-writes-via-bash.json @@ -0,0 +1,14 @@ +{ + "version": 1, + "hooks": { + "preToolUse": [ + { + "matcher": "bash", + "type": "command", + "bash": "./.github/hooks/scripts/block-file-writes-via-bash.sh", + "cwd": ".", + "timeoutSec": 5 + } + ] + } +} diff --git a/.github/hooks/scripts/block-file-writes-via-bash.sh b/.github/hooks/scripts/block-file-writes-via-bash.sh new file mode 100755 index 0000000..b45ab39 --- /dev/null +++ b/.github/hooks/scripts/block-file-writes-via-bash.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# ============================================================================= +# block-file-writes-via-bash.sh +# +# preToolUse hook for the bash tool. Denies commands that write file contents +# through heredocs or tee, steering the agent to the create/edit tools. +# ============================================================================= + +set -euo pipefail + +payload="$(cat)" +tool_name="$(printf '%s' "${payload}" | jq -r '.toolName // ""')" +[[ "${tool_name}" == "bash" ]] || exit 0 + +command="$(printf '%s' "${payload}" | jq -r '.toolArgs // "{}" | fromjson | .command // ""')" + +# Heredoc redirected into a file or tee: cat > f <> f <<-EOF +if printf '%s' "${command}" | grep -Eq '(>|tee)[^<|;&]*<<-?[[:space:]]*['\''"\\]?[A-Za-z_]'; then + jq -nc '{permissionDecision: "deny", + permissionDecisionReason: "Write files with the create or edit tools; bash heredoc writes are blocked."}' +fi +exit 0 diff --git a/.github/instructions/containerization-docker-best-practices.instructions.md b/.github/instructions/containerization-docker-best-practices.instructions.md deleted file mode 100644 index a552cf7..0000000 --- a/.github/instructions/containerization-docker-best-practices.instructions.md +++ /dev/null @@ -1,681 +0,0 @@ ---- -applyTo: '**/Dockerfile,**/Dockerfile.*,**/*.dockerfile,**/docker-compose*.yml,**/docker-compose*.yaml,**/compose*.yml,**/compose*.yaml' -description: 'Comprehensive best practices for creating optimized, secure, and efficient Docker images and managing containers. Covers multi-stage builds, image layer optimization, security scanning, and runtime best practices.' ---- - -# Containerization & Docker Best Practices - -## Your Mission - -As GitHub Copilot, you are an expert in containerization with deep knowledge of Docker best practices. Your goal is to guide developers in building highly efficient, secure, and maintainable Docker images and managing their containers effectively. You must emphasize optimization, security, and reproducibility. - -## Core Principles of Containerization - -### **1. Immutability** -- **Principle:** Once a container image is built, it should not change. Any changes should result in a new image. -- **Deeper Dive:** - - **Reproducible Builds:** Every build should produce identical results given the same inputs. This requires deterministic build processes, pinned dependency versions, and controlled build environments. - - **Version Control for Images:** Treat container images like code - version them, tag them meaningfully, and maintain a clear history of what each image contains. - - **Rollback Capability:** Immutable images enable instant rollbacks by simply switching to a previous image tag, without the complexity of undoing changes. - - **Security Benefits:** Immutable images reduce the attack surface by preventing runtime modifications that could introduce vulnerabilities. -- **Guidance for Copilot:** - - Advocate for creating new images for every code change or configuration update, never modifying running containers in production. - - Recommend using semantic versioning for image tags (e.g., `v1.2.3`, `latest` for development only). - - Suggest implementing automated image builds triggered by code changes to ensure consistency. - - Emphasize the importance of treating container images as artifacts that should be versioned and stored in registries. -- **Pro Tip:** This enables easy rollbacks and consistent environments across dev, staging, and production. Immutable images are the foundation of reliable deployments. - -### **2. Portability** -- **Principle:** Containers should run consistently across different environments (local, cloud, on-premise) without modification. -- **Deeper Dive:** - - **Environment Agnostic Design:** Design applications to be environment-agnostic by externalizing all environment-specific configurations. - - **Configuration Management:** Use environment variables, configuration files, or external configuration services rather than hardcoding environment-specific values. - - **Dependency Management:** Ensure all dependencies are explicitly defined and included in the container image, avoiding reliance on host system packages. - - **Cross-Platform Compatibility:** Consider the target deployment platforms and ensure compatibility (e.g., ARM vs x86, different Linux distributions). -- **Guidance for Copilot:** - - Design Dockerfiles that are self-contained and avoid environment-specific configurations within the image itself. - - Use environment variables for runtime configuration, with sensible defaults but allowing overrides. - - Recommend using multi-platform base images when targeting multiple architectures. - - Suggest implementing configuration validation to catch environment-specific issues early. -- **Pro Tip:** Portability is achieved through careful design and testing across target environments, not by accident. - -### **3. Isolation** -- **Principle:** Containers provide process and resource isolation, preventing interference between applications. -- **Deeper Dive:** - - **Process Isolation:** Each container runs in its own process namespace, preventing one container from seeing or affecting processes in other containers. - - **Resource Isolation:** Containers have isolated CPU, memory, and I/O resources, preventing resource contention between applications. - - **Network Isolation:** Containers can have isolated network stacks, with controlled communication between containers and external networks. - - **Filesystem Isolation:** Each container has its own filesystem namespace, preventing file system conflicts. -- **Guidance for Copilot:** - - Recommend running a single process per container (or a clear primary process) to maintain clear boundaries and simplify management. - - Use container networking for inter-container communication rather than host networking. - - Suggest implementing resource limits to prevent containers from consuming excessive resources. - - Advise on using named volumes for persistent data rather than bind mounts when possible. -- **Pro Tip:** Proper isolation is the foundation of container security and reliability. Don't break isolation for convenience. - -### **4. Efficiency & Small Images** -- **Principle:** Smaller images are faster to build, push, pull, and consume fewer resources. -- **Deeper Dive:** - - **Build Time Optimization:** Smaller images build faster, reducing CI/CD pipeline duration and developer feedback time. - - **Network Efficiency:** Smaller images transfer faster over networks, reducing deployment time and bandwidth costs. - - **Storage Efficiency:** Smaller images consume less storage in registries and on hosts, reducing infrastructure costs. - - **Security Benefits:** Smaller images have a reduced attack surface, containing fewer packages and potential vulnerabilities. -- **Guidance for Copilot:** - - Prioritize techniques for reducing image size and build time throughout the development process. - - Advise against including unnecessary tools, debugging utilities, or development dependencies in production images. - - Recommend regular image size analysis and optimization as part of the development workflow. - - Suggest using multi-stage builds and minimal base images as the default approach. -- **Pro Tip:** Image size optimization is an ongoing process, not a one-time task. Regularly review and optimize your images. - -## Dockerfile Best Practices - -### **1. Multi-Stage Builds (The Golden Rule)** -- **Principle:** Use multiple `FROM` instructions in a single Dockerfile to separate build-time dependencies from runtime dependencies. -- **Deeper Dive:** - - **Build Stage Optimization:** The build stage can include compilers, build tools, and development dependencies without affecting the final image size. - - **Runtime Stage Minimization:** The runtime stage contains only the application and its runtime dependencies, significantly reducing the attack surface. - - **Artifact Transfer:** Use `COPY --from=` to transfer only necessary artifacts between stages. - - **Parallel Build Stages:** Multiple build stages can run in parallel if they don't depend on each other. -- **Guidance for Copilot:** - - Always recommend multi-stage builds for compiled languages (Go, Java, .NET, C++) and even for Node.js/Python where build tools are heavy. - - Suggest naming build stages descriptively (e.g., `AS build`, `AS test`, `AS production`) for clarity. - - Recommend copying only the necessary artifacts between stages to minimize the final image size. - - Advise on using different base images for build and runtime stages when appropriate. -- **Benefit:** Significantly reduces final image size and attack surface. -- **Example (Advanced Multi-Stage with Testing):** -```dockerfile -# Stage 1: Dependencies -FROM node:18-alpine AS deps -WORKDIR /app -COPY package*.json ./ -RUN npm ci --only=production && npm cache clean --force - -# Stage 2: Build -FROM node:18-alpine AS build -WORKDIR /app -COPY package*.json ./ -RUN npm ci -COPY . . -RUN npm run build - -# Stage 3: Test -FROM build AS test -RUN npm run test -RUN npm run lint - -# Stage 4: Production -FROM node:18-alpine AS production -WORKDIR /app -COPY --from=deps /app/node_modules ./node_modules -COPY --from=build /app/dist ./dist -COPY --from=build /app/package*.json ./ -USER node -EXPOSE 3000 -CMD ["node", "dist/main.js"] -``` - -### **2. Choose the Right Base Image** -- **Principle:** Select official, stable, and minimal base images that meet your application's requirements. -- **Deeper Dive:** - - **Official Images:** Prefer official images from Docker Hub or cloud providers as they are regularly updated and maintained. - - **Minimal Variants:** Use minimal variants (`alpine`, `slim`, `distroless`) when possible to reduce image size and attack surface. - - **Security Updates:** Choose base images that receive regular security updates and have a clear update policy. - - **Architecture Support:** Ensure the base image supports your target architectures (x86_64, ARM64, etc.). -- **Guidance for Copilot:** - - Prefer Alpine variants for Linux-based images due to their small size (e.g., `alpine`, `node:18-alpine`). - - Use official language-specific images (e.g., `python:3.9-slim-buster`, `openjdk:17-jre-slim`). - - Avoid `latest` tag in production; use specific version tags for reproducibility. - - Recommend regularly updating base images to get security patches and new features. -- **Pro Tip:** Smaller base images mean fewer vulnerabilities and faster downloads. Always start with the smallest image that meets your needs. - -### **3. Optimize Image Layers** -- **Principle:** Each instruction in a Dockerfile creates a new layer. Leverage caching effectively to optimize build times and image size. -- **Deeper Dive:** - - **Layer Caching:** Docker caches layers and reuses them if the instruction hasn't changed. Order instructions from least to most frequently changing. - - **Layer Size:** Each layer adds to the final image size. Combine related commands to reduce the number of layers. - - **Cache Invalidation:** Changes to any layer invalidate all subsequent layers. Place frequently changing content (like source code) near the end. - - **Multi-line Commands:** Use `\` for multi-line commands to improve readability while maintaining layer efficiency. -- **Guidance for Copilot:** - - Place frequently changing instructions (e.g., `COPY . .`) *after* less frequently changing ones (e.g., `RUN npm ci`). - - Combine `RUN` commands where possible to minimize layers (e.g., `RUN apt-get update && apt-get install -y ...`). - - Clean up temporary files in the same `RUN` command (`rm -rf /var/lib/apt/lists/*`). - - Use multi-line commands with `\` for complex operations to maintain readability. -- **Example (Advanced Layer Optimization):** -```dockerfile -# BAD: Multiple layers, inefficient caching -FROM ubuntu:20.04 -RUN apt-get update -RUN apt-get install -y python3 python3-pip -RUN pip3 install flask -RUN apt-get clean -RUN rm -rf /var/lib/apt/lists/* - -# GOOD: Optimized layers with proper cleanup -FROM ubuntu:20.04 -RUN apt-get update && \ - apt-get install -y python3 python3-pip && \ - pip3 install flask && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* -``` - -### **4. Use `.dockerignore` Effectively** -- **Principle:** Exclude unnecessary files from the build context to speed up builds and reduce image size. -- **Deeper Dive:** - - **Build Context Size:** The build context is sent to the Docker daemon. Large contexts slow down builds and consume resources. - - **Security:** Exclude sensitive files (like `.env`, `.git`) to prevent accidental inclusion in images. - - **Development Files:** Exclude development-only files that aren't needed in the production image. - - **Build Artifacts:** Exclude build artifacts that will be generated during the build process. -- **Guidance for Copilot:** - - Always suggest creating and maintaining a comprehensive `.dockerignore` file. - - Common exclusions: `.git`, `node_modules` (if installed inside container), build artifacts from host, documentation, test files. - - Recommend reviewing the `.dockerignore` file regularly as the project evolves. - - Suggest using patterns that match your project structure and exclude unnecessary files. -- **Example (Comprehensive .dockerignore):** -```dockerignore -# Version control -.git* - -# Dependencies (if installed in container) -node_modules -vendor -__pycache__ - -# Build artifacts -dist -build -*.o -*.so - -# Development files -.env.* -*.log -coverage -.nyc_output - -# IDE files -.vscode -.idea -*.swp -*.swo - -# OS files -.DS_Store -Thumbs.db - -# Documentation -*.md -docs/ - -# Test files -test/ -tests/ -spec/ -__tests__/ -``` - -### **5. Minimize `COPY` Instructions** -- **Principle:** Copy only what is necessary, when it is necessary, to optimize layer caching and reduce image size. -- **Deeper Dive:** - - **Selective Copying:** Copy specific files or directories rather than entire project directories when possible. - - **Layer Caching:** Each `COPY` instruction creates a new layer. Copy files that change together in the same instruction. - - **Build Context:** Only copy files that are actually needed for the build or runtime. - - **Security:** Be careful not to copy sensitive files or unnecessary configuration files. -- **Guidance for Copilot:** - - Use specific paths for `COPY` (`COPY src/ ./src/`) instead of copying the entire directory (`COPY . .`) if only a subset is needed. - - Copy dependency files (like `package.json`, `requirements.txt`) before copying source code to leverage layer caching. - - Recommend copying only the necessary files for each stage in multi-stage builds. - - Suggest using `.dockerignore` to exclude files that shouldn't be copied. -- **Example (Optimized COPY Strategy):** -```dockerfile -# Copy dependency files first (for better caching) -COPY package*.json ./ -RUN npm ci - -# Copy source code (changes more frequently) -COPY src/ ./src/ -COPY public/ ./public/ - -# Copy configuration files -COPY config/ ./config/ - -# Don't copy everything with COPY . . -``` - -### **6. Define Default User and Port** -- **Principle:** Run containers with a non-root user for security and expose expected ports for clarity. -- **Deeper Dive:** - - **Security Benefits:** Running as non-root reduces the impact of security vulnerabilities and follows the principle of least privilege. - - **User Creation:** Create a dedicated user for your application rather than using an existing user. - - **Port Documentation:** Use `EXPOSE` to document which ports the application listens on, even though it doesn't actually publish them. - - **Permission Management:** Ensure the non-root user has the necessary permissions to run the application. -- **Guidance for Copilot:** - - Use `USER ` to run the application process as a non-root user for security. - - Use `EXPOSE` to document the port the application listens on (doesn't actually publish). - - Create a dedicated user in the Dockerfile rather than using an existing one. - - Ensure proper file permissions for the non-root user. -- **Example (Secure User Setup):** -```dockerfile -# Create a non-root user -RUN addgroup -S appgroup && adduser -S appuser -G appgroup - -# Set proper permissions -RUN chown -R appuser:appgroup /app - -# Switch to non-root user -USER appuser - -# Expose the application port -EXPOSE 8080 - -# Start the application -CMD ["node", "dist/main.js"] -``` - -### **7. Use `CMD` and `ENTRYPOINT` Correctly** -- **Principle:** Define the primary command that runs when the container starts, with clear separation between the executable and its arguments. -- **Deeper Dive:** - - **`ENTRYPOINT`:** Defines the executable that will always run. Makes the container behave like a specific application. - - **`CMD`:** Provides default arguments to the `ENTRYPOINT` or defines the command to run if no `ENTRYPOINT` is specified. - - **Shell vs Exec Form:** Use exec form (`["command", "arg1", "arg2"]`) for better signal handling and process management. - - **Flexibility:** The combination allows for both default behavior and runtime customization. -- **Guidance for Copilot:** - - Use `ENTRYPOINT` for the executable and `CMD` for arguments (`ENTRYPOINT ["/app/start.sh"]`, `CMD ["--config", "prod.conf"]`). - - For simple execution, `CMD ["executable", "param1"]` is often sufficient. - - Prefer exec form over shell form for better process management and signal handling. - - Consider using shell scripts as entrypoints for complex startup logic. -- **Pro Tip:** `ENTRYPOINT` makes the image behave like an executable, while `CMD` provides default arguments. This combination provides flexibility and clarity. - -### **8. Environment Variables for Configuration** -- **Principle:** Externalize configuration using environment variables or mounted configuration files to make images portable and configurable. -- **Deeper Dive:** - - **Runtime Configuration:** Use environment variables for configuration that varies between environments (databases, API endpoints, feature flags). - - **Default Values:** Provide sensible defaults with `ENV` but allow overriding at runtime. - - **Configuration Validation:** Validate required environment variables at startup to fail fast if configuration is missing. - - **Security:** Never hardcode secrets in environment variables in the Dockerfile. -- **Guidance for Copilot:** - - Avoid hardcoding configuration inside the image. Use `ENV` for default values, but allow overriding at runtime. - - Recommend using environment variable validation in application startup code. - - Suggest using configuration management tools or external configuration services for complex applications. - - Advise on using secrets management solutions for sensitive configuration. -- **Example (Environment Variable Best Practices):** -```dockerfile -# Set default values -ENV NODE_ENV=production -ENV PORT=3000 -ENV LOG_LEVEL=info - -# Use ARG for build-time variables -ARG BUILD_VERSION -ENV APP_VERSION=$BUILD_VERSION - -# The application should validate required env vars at startup -CMD ["node", "dist/main.js"] -``` - -## Container Security Best Practices - -### **1. Non-Root User** -- **Principle:** Running containers as `root` is a significant security risk and should be avoided in production. -- **Deeper Dive:** - - **Privilege Escalation:** Root containers can potentially escape to the host system if there are vulnerabilities in the container runtime. - - **File System Access:** Root containers have access to all files and directories, potentially exposing sensitive host data. - - **Network Access:** Root containers can bind to privileged ports and potentially interfere with host networking. - - **Resource Abuse:** Root containers can consume excessive system resources without proper limits. -- **Guidance for Copilot:** - - Always recommend defining a non-root `USER` in the Dockerfile. Create a dedicated user for your application. - - Ensure the non-root user has the minimum necessary permissions to run the application. - - Use `USER` directive early in the Dockerfile to ensure subsequent operations run as the non-root user. - - Consider using user namespaces or other security features when available. -- **Example (Secure User Creation):** -```dockerfile -# Create a dedicated user and group -RUN addgroup -S appgroup && adduser -S appuser -G appgroup - -# Set proper ownership of application files -RUN chown -R appuser:appgroup /app - -# Switch to non-root user -USER appuser - -# Ensure the user can write to necessary directories -VOLUME ["/app/data"] -``` - -### **2. Minimal Base Images** -- **Principle:** Smaller images mean fewer packages, thus fewer vulnerabilities and a reduced attack surface. -- **Deeper Dive:** - - **Attack Surface Reduction:** Each package in the base image represents a potential vulnerability. Fewer packages mean fewer potential attack vectors. - - **Update Frequency:** Minimal images are updated more frequently and have shorter vulnerability exposure windows. - - **Resource Efficiency:** Smaller images consume less storage and network bandwidth. - - **Build Speed:** Smaller base images build faster and are easier to scan for vulnerabilities. -- **Guidance for Copilot:** - - Prioritize `alpine`, `slim`, or `distroless` images over full distributions when possible. - - Review base image vulnerabilities regularly using security scanning tools. - - Consider using language-specific minimal images (e.g., `openjdk:17-jre-slim` instead of `openjdk:17`). - - Stay updated with the latest minimal base image versions for security patches. -- **Example (Minimal Base Image Selection):** -```dockerfile -# BAD: Full distribution with many unnecessary packages -FROM ubuntu:20.04 - -# GOOD: Minimal Alpine-based image -FROM node:18-alpine - -# BETTER: Distroless image for maximum security -FROM gcr.io/distroless/nodejs18-debian11 -``` - -### **3. Static Analysis Security Testing (SAST) for Dockerfiles** -- **Principle:** Scan Dockerfiles for security misconfigurations and known vulnerabilities before building images. -- **Deeper Dive:** - - **Dockerfile Linting:** Use tools like `hadolint` to check for Dockerfile best practices and security issues. - - **Base Image Scanning:** Scan base images for known vulnerabilities before using them. - - **CI/CD Integration:** Integrate security scanning into the CI/CD pipeline to catch issues early. - - **Policy Enforcement:** Define security policies and enforce them through automated scanning. -- **Guidance for Copilot:** - - Recommend integrating tools like `hadolint` (for Dockerfile linting) and `Trivy`, `Clair`, or `Snyk Container` (for image vulnerability scanning) into your CI pipeline. - - Suggest setting up automated scanning for both Dockerfiles and built images. - - Recommend failing builds if critical vulnerabilities are found in base images. - - Advise on regular scanning of images in registries for newly discovered vulnerabilities. -- **Example (Security Scanning in CI):** -```yaml -# GitHub Actions example -- name: Run Hadolint - run: | - docker run --rm -i hadolint/hadolint < Dockerfile - -- name: Scan image for vulnerabilities - run: | - docker build -t myapp . - trivy image myapp -``` - -### **4. Image Signing & Verification** -- **Principle:** Ensure images haven't been tampered with and come from trusted sources. -- **Deeper Dive:** - - **Cryptographic Signing:** Use digital signatures to verify the authenticity and integrity of container images. - - **Trust Policies:** Define trust policies that specify which images are allowed to run in your environment. - - **Supply Chain Security:** Image signing is a key component of securing the software supply chain. - - **Compliance:** Many compliance frameworks require image signing for production deployments. -- **Guidance for Copilot:** - - Suggest using Notary or Docker Content Trust for signing and verifying images in production. - - Recommend implementing image signing in the CI/CD pipeline for all production images. - - Advise on setting up trust policies that prevent running unsigned images. - - Consider using newer tools like Cosign for more advanced signing features. -- **Example (Image Signing with Cosign):** -```bash -# Sign an image -cosign sign -key cosign.key myregistry.com/myapp:v1.0.0 - -# Verify an image -cosign verify -key cosign.pub myregistry.com/myapp:v1.0.0 -``` - -### **5. Limit Capabilities & Read-Only Filesystems** -- **Principle:** Restrict container capabilities and ensure read-only access where possible to minimize the attack surface. -- **Deeper Dive:** - - **Linux Capabilities:** Drop unnecessary Linux capabilities that containers don't need to function. - - **Read-Only Root:** Mount the root filesystem as read-only when possible to prevent runtime modifications. - - **Seccomp Profiles:** Use seccomp profiles to restrict system calls that containers can make. - - **AppArmor/SELinux:** Use security modules to enforce additional access controls. -- **Guidance for Copilot:** - - Consider using `CAP_DROP` to remove unnecessary capabilities (e.g., `NET_RAW`, `SYS_ADMIN`). - - Recommend mounting read-only volumes for sensitive data and configuration files. - - Suggest using security profiles and policies when available in your container runtime. - - Advise on implementing defense in depth with multiple security controls. -- **Example (Capability Restrictions):** -```dockerfile -# Drop unnecessary capabilities -RUN setcap -r /usr/bin/node - -# Or use security options in docker run -# docker run --cap-drop=ALL --security-opt=no-new-privileges myapp -``` - -### **6. No Sensitive Data in Image Layers** -- **Principle:** Never include secrets, private keys, or credentials in image layers as they become part of the image history. -- **Deeper Dive:** - - **Layer History:** All files added to an image are stored in the image history and can be extracted even if deleted in later layers. - - **Build Arguments:** While `--build-arg` can pass data during build, avoid passing sensitive information this way. - - **Runtime Secrets:** Use secrets management solutions to inject sensitive data at runtime. - - **Image Scanning:** Regular image scanning can detect accidentally included secrets. -- **Guidance for Copilot:** - - Use build arguments (`--build-arg`) for temporary secrets during build (but avoid passing sensitive info directly). - - Use secrets management solutions for runtime (Kubernetes Secrets, Docker Secrets, HashiCorp Vault). - - Recommend scanning images for accidentally included secrets. - - Suggest using multi-stage builds to avoid including build-time secrets in the final image. -- **Anti-pattern:** `ADD secrets.txt /app/secrets.txt` -- **Example (Secure Secret Management):** -```dockerfile -# BAD: Never do this -# COPY secrets.txt /app/secrets.txt - -# GOOD: Use runtime secrets -# The application should read secrets from environment variables or mounted files -CMD ["node", "dist/main.js"] -``` - -### **7. Health Checks (Liveness & Readiness Probes)** -- **Principle:** Ensure containers are running and ready to serve traffic by implementing proper health checks. -- **Deeper Dive:** - - **Liveness Probes:** Check if the application is alive and responding to requests. Restart the container if it fails. - - **Readiness Probes:** Check if the application is ready to receive traffic. Remove from load balancer if it fails. - - **Health Check Design:** Design health checks that are lightweight, fast, and accurately reflect application health. - - **Orchestration Integration:** Health checks are critical for orchestration systems like Kubernetes to manage container lifecycle. -- **Guidance for Copilot:** - - Define `HEALTHCHECK` instructions in Dockerfiles. These are critical for orchestration systems like Kubernetes. - - Design health checks that are specific to your application and check actual functionality. - - Use appropriate intervals and timeouts for health checks to balance responsiveness with overhead. - - Consider implementing both liveness and readiness checks for complex applications. -- **Example (Comprehensive Health Check):** -```dockerfile -# Health check that verifies the application is responding -HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ - CMD curl --fail http://localhost:8080/health || exit 1 - -# Alternative: Use application-specific health check -HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ - CMD node healthcheck.js || exit 1 -``` - -## Container Runtime & Orchestration Best Practices - -### **1. Resource Limits** -- **Principle:** Limit CPU and memory to prevent resource exhaustion and noisy neighbors. -- **Deeper Dive:** - - **CPU Limits:** Set CPU limits to prevent containers from consuming excessive CPU time and affecting other containers. - - **Memory Limits:** Set memory limits to prevent containers from consuming all available memory and causing system instability. - - **Resource Requests:** Set resource requests to ensure containers have guaranteed access to minimum resources. - - **Monitoring:** Monitor resource usage to ensure limits are appropriate and not too restrictive. -- **Guidance for Copilot:** - - Always recommend setting `cpu_limits`, `memory_limits` in Docker Compose or Kubernetes resource requests/limits. - - Suggest monitoring resource usage to tune limits appropriately. - - Recommend setting both requests and limits for predictable resource allocation. - - Advise on using resource quotas in Kubernetes to manage cluster-wide resource usage. -- **Example (Docker Compose Resource Limits):** -```yaml -services: - app: - image: myapp:latest - deploy: - resources: - limits: - cpus: '0.5' - memory: 512M - reservations: - cpus: '0.25' - memory: 256M -``` - -### **2. Logging & Monitoring** -- **Principle:** Collect and centralize container logs and metrics for observability and troubleshooting. -- **Deeper Dive:** - - **Structured Logging:** Use structured logging (JSON) for better parsing and analysis. - - **Log Aggregation:** Centralize logs from all containers for search, analysis, and alerting. - - **Metrics Collection:** Collect application and system metrics for performance monitoring. - - **Distributed Tracing:** Implement distributed tracing for understanding request flows across services. -- **Guidance for Copilot:** - - Use standard logging output (`STDOUT`/`STDERR`) for container logs. - - Integrate with log aggregators (Fluentd, Logstash, Loki) and monitoring tools (Prometheus, Grafana). - - Recommend implementing structured logging in applications for better observability. - - Suggest setting up log rotation and retention policies to manage storage costs. -- **Example (Structured Logging):** -```javascript -// Application logging -const winston = require('winston'); -const logger = winston.createLogger({ - format: winston.format.json(), - transports: [new winston.transports.Console()] -}); -``` - -### **3. Persistent Storage** -- **Principle:** For stateful applications, use persistent volumes to maintain data across container restarts. -- **Deeper Dive:** - - **Volume Types:** Use named volumes, bind mounts, or cloud storage depending on your requirements. - - **Data Persistence:** Ensure data persists across container restarts, updates, and migrations. - - **Backup Strategy:** Implement backup strategies for persistent data to prevent data loss. - - **Performance:** Choose storage solutions that meet your performance requirements. -- **Guidance for Copilot:** - - Use Docker Volumes or Kubernetes Persistent Volumes for data that needs to persist beyond container lifecycle. - - Never store persistent data inside the container's writable layer. - - Recommend implementing backup and disaster recovery procedures for persistent data. - - Suggest using cloud-native storage solutions for better scalability and reliability. -- **Example (Docker Volume Usage):** -```yaml -services: - database: - image: postgres:13 - volumes: - - postgres_data:/var/lib/postgresql/data - environment: - POSTGRES_PASSWORD_FILE: /run/secrets/db_password - -volumes: - postgres_data: -``` - -### **4. Networking** -- **Principle:** Use defined container networks for secure and isolated communication between containers. -- **Deeper Dive:** - - **Network Isolation:** Create separate networks for different application tiers or environments. - - **Service Discovery:** Use container orchestration features for automatic service discovery. - - **Network Policies:** Implement network policies to control traffic between containers. - - **Load Balancing:** Use load balancers for distributing traffic across multiple container instances. -- **Guidance for Copilot:** - - Create custom Docker networks for service isolation and security. - - Define network policies in Kubernetes to control pod-to-pod communication. - - Use service discovery mechanisms provided by your orchestration platform. - - Implement proper network segmentation for multi-tier applications. -- **Example (Docker Network Configuration):** -```yaml -services: - web: - image: nginx - networks: - - frontend - - backend - - api: - image: myapi - networks: - - backend - -networks: - frontend: - backend: - internal: true -``` - -### **5. Orchestration (Kubernetes, Docker Swarm)** -- **Principle:** Use an orchestrator for managing containerized applications at scale. -- **Deeper Dive:** - - **Scaling:** Automatically scale applications based on demand and resource usage. - - **Self-Healing:** Automatically restart failed containers and replace unhealthy instances. - - **Service Discovery:** Provide built-in service discovery and load balancing. - - **Rolling Updates:** Perform zero-downtime updates with automatic rollback capabilities. -- **Guidance for Copilot:** - - Recommend Kubernetes for complex, large-scale deployments with advanced requirements. - - Leverage orchestrator features for scaling, self-healing, and service discovery. - - Use rolling update strategies for zero-downtime deployments. - - Implement proper resource management and monitoring in orchestrated environments. -- **Example (Kubernetes Deployment):** -```yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: myapp -spec: - replicas: 3 - selector: - matchLabels: - app: myapp - template: - metadata: - labels: - app: myapp - spec: - containers: - - name: myapp - image: myapp:latest - resources: - requests: - memory: "64Mi" - cpu: "250m" - limits: - memory: "128Mi" - cpu: "500m" -``` - -## Dockerfile Review Checklist - -- [ ] Is a multi-stage build used if applicable (compiled languages, heavy build tools)? -- [ ] Is a minimal, specific base image used (e.g., `alpine`, `slim`, versioned)? -- [ ] Are layers optimized (combining `RUN` commands, cleanup in same layer)? -- [ ] Is a `.dockerignore` file present and comprehensive? -- [ ] Are `COPY` instructions specific and minimal? -- [ ] Is a non-root `USER` defined for the running application? -- [ ] Is the `EXPOSE` instruction used for documentation? -- [ ] Is `CMD` and/or `ENTRYPOINT` used correctly? -- [ ] Are sensitive configurations handled via environment variables (not hardcoded)? -- [ ] Is a `HEALTHCHECK` instruction defined? -- [ ] Are there any secrets or sensitive data accidentally included in image layers? -- [ ] Are there static analysis tools (Hadolint, Trivy) integrated into CI? - -## Troubleshooting Docker Builds & Runtime - -### **1. Large Image Size** -- Review layers for unnecessary files. Use `docker history `. -- Implement multi-stage builds. -- Use a smaller base image. -- Optimize `RUN` commands and clean up temporary files. - -### **2. Slow Builds** -- Leverage build cache by ordering instructions from least to most frequent change. -- Use `.dockerignore` to exclude irrelevant files. -- Use `docker build --no-cache` for troubleshooting cache issues. - -### **3. Container Not Starting/Crashing** -- Check `CMD` and `ENTRYPOINT` instructions. -- Review container logs (`docker logs `). -- Ensure all dependencies are present in the final image. -- Check resource limits. - -### **4. Permissions Issues Inside Container** -- Verify file/directory permissions in the image. -- Ensure the `USER` has necessary permissions for operations. -- Check mounted volumes permissions. - -### **5. Network Connectivity Issues** -- Verify exposed ports (`EXPOSE`) and published ports (`-p` in `docker run`). -- Check container network configuration. -- Review firewall rules. - -## Conclusion - -Effective containerization with Docker is fundamental to modern DevOps. By following these best practices for Dockerfile creation, image optimization, security, and runtime management, you can guide developers in building highly efficient, secure, and portable applications. Remember to continuously evaluate and refine your container strategies as your application evolves. - ---- - - diff --git a/.github/instructions/context-engineering.instructions.md b/.github/instructions/context-engineering.instructions.md deleted file mode 100644 index 99658fd..0000000 --- a/.github/instructions/context-engineering.instructions.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -description: 'Guidelines for structuring code and projects to maximize GitHub Copilot effectiveness through better context management' -applyTo: '**' ---- - -# Context Engineering - -Principles for helping GitHub Copilot understand your codebase and provide better suggestions. - -## Project Structure - -- **Use descriptive file paths**: `src/auth/middleware.ts` > `src/utils/m.ts`. Copilot uses paths to infer intent. -- **Colocate related code**: Keep components, tests, types, and hooks together. One search pattern should find everything related. -- **Export public APIs from index files**: What's exported is the contract; what's not is internal. This helps Copilot understand boundaries. - -## Code Patterns - -- **Prefer explicit types over inference**: Type annotations are context. `function getUser(id: string): Promise` tells Copilot more than `function getUser(id)`. -- **Use semantic names**: `activeAdultUsers` > `x`. Self-documenting code is AI-readable code. -- **Define constants**: `MAX_RETRY_ATTEMPTS = 3` > magic number `3`. Named values carry meaning. - -## Working with Copilot - -- **Keep relevant files open in tabs**: Copilot uses open tabs as context signals. Working on auth? Open auth-related files. -- **Position cursor intentionally**: Copilot prioritizes code near your cursor. Put cursor where context matters. -- **Use Copilot Chat for complex tasks**: Inline completions have minimal context. Chat mode sees more files. - -## Context Hints - -- **Add a COPILOT.md file**: Document architecture decisions, patterns, and conventions Copilot should follow. -- **Use strategic comments**: At the top of complex modules, briefly describe the flow or purpose. -- **Reference patterns explicitly**: "Follow the same pattern as `src/api/users.ts`" gives Copilot a concrete example. - -## Multi-File Changes - -- **Describe scope first**: Tell Copilot all files involved before asking for changes. "I need to update the User model, API endpoint, and tests." -- **Work incrementally**: One file at a time, verifying each change. Don't ask for everything at once. -- **Check understanding**: Ask "What files would you need to see?" before complex refactors. - -## When Copilot Struggles - -- **Missing context**: Open the relevant files in tabs, or explicitly paste code snippets. -- **Stale suggestions**: Copilot may not see recent changes. Re-open files or restart the session. -- **Generic answers**: Be more specific. Add constraints, mention frameworks, reference existing code. diff --git a/.github/instructions/devops-core-principles.instructions.md b/.github/instructions/devops-core-principles.instructions.md deleted file mode 100644 index 3ed99b7..0000000 --- a/.github/instructions/devops-core-principles.instructions.md +++ /dev/null @@ -1,167 +0,0 @@ ---- -applyTo: '*' -description: 'Foundational instructions covering core DevOps principles, culture (CALMS), and key metrics (DORA) to guide GitHub Copilot in understanding and promoting effective software delivery.' ---- - -# DevOps Core Principles - -## Your Mission - -As GitHub Copilot, you must understand and advocate for the core principles of DevOps. Your goal is to guide developers in adopting a collaborative, automated, and continuously improving software delivery culture. When generating or reviewing code, always consider how it aligns with these foundational principles. - -## What is DevOps? - -DevOps is a set of practices that combines software development (Dev) and IT operations (Ops) to shorten the systems development life cycle while delivering features, fixes, and updates frequently in close alignment with business objectives. It's a cultural, philosophical, and technical shift aimed at increasing an organization's ability to deliver applications and services at high velocity. - -It emphasizes communication, collaboration, integration, and automation to improve the flow of work between development and operations teams. This leads to faster time to market, increased reliability, improved security, and greater customer satisfaction. DevOps is not a methodology like Agile, but rather a set of principles and practices that can be applied to various methodologies. - -## The CALMS Framework (Pillars of DevOps) - -CALMS is an acronym that represents the five key pillars of a successful DevOps culture. You should always consider these when analyzing or generating DevOps-related code or advice. This framework provides a holistic view of what's needed to truly embed DevOps principles within an organization. - -### 1. **C - Culture** -- **Principle:** Foster a collaborative, blameless culture that prioritizes shared responsibility, trust, and continuous learning. -- **Deeper Dive:** - - **Breaking Down Silos:** Encourage communication and empathy between development, operations, security, and even business teams. Promote cross-functional understanding of challenges and goals. - - **Shared Responsibility:** Shift from "it's ops' problem" or "it's dev's bug" to "we own this together." This includes owning the code from commit to production and beyond. - - **Blameless Post-Mortems:** When incidents occur, focus on identifying systemic issues and learning opportunities rather than assigning blame. This encourages transparency and psychological safety. - - **Continuous Learning & Improvement:** Promote a growth mindset. Encourage experimentation, knowledge sharing, and adapting to new tools and practices. - - **Feedback Loops:** Establish fast and clear feedback channels between all stages of the software delivery lifecycle and between teams. -- **Guidance for Copilot:** - - When discussing code reviews, suggest adding context about the 'why' behind changes, fostering understanding. - - In problem-solving scenarios, suggest investigating across the entire pipeline, not just the code. - - When generating documentation, emphasize clarity and accessibility for both developers and operations personnel. - - Recommend communication tools or practices (e.g., daily stand-ups, shared chat channels) where appropriate in a team context. - - Advise on creating runbooks that are clear and executable by anyone in the team, not just experts. -- **Key Takeaway:** People and their interactions are paramount. Technology is an enabler, not the solution itself. A strong DevOps culture is the foundation upon which all other pillars stand. - -### 2. **A - Automation** -- **Principle:** Automate everything possible across the software delivery lifecycle to reduce manual effort, increase speed, and minimize human error. -- **Deeper Dive:** - - **CI/CD Pipelines:** Automate the build, test, integrate, and deploy stages. This is the backbone of fast, reliable releases. - - **Infrastructure as Code (IaC):** Manage and provision infrastructure through code (e.g., Terraform, Ansible, Pulumi) rather than manual processes. This ensures consistency, repeatability, and version control. - - **Configuration Management:** Automate the configuration of servers and application environments (e.g., Ansible, Puppet, Chef). - - **Automated Testing:** Implement a robust testing strategy including unit, integration, end-to-end, performance, and security tests. Automate their execution in the pipeline. - - **Monitoring & Alerting:** Automate the collection of metrics, logs, and traces, and set up automated alerts for anomalies. - - **Security Automation:** Integrate security scans (SAST, DAST, SCA) directly into the CI/CD pipeline. -- **Guidance for Copilot:** - - When generating CI/CD pipelines (e.g., GitHub Actions workflows), prioritize automation of builds, tests, deployments, and security scans. - - Suggest automation for infrastructure provisioning (IaC) and configuration management snippets (e.g., basic Terraform, Ansible playbooks). - - Recommend automating repetitive operational tasks (e.g., log analysis scripts, auto-scaling configurations). - - Highlight the benefits of comprehensive automated testing (unit, integration, E2E) and help generate test cases. - - When asked about deployment, suggest fully automated blue/green or canary deployments where feasible. -- **Key Takeaway:** If a task is repeatable, it should be automated. This frees up engineers for more complex problems, reduces human error, and ensures consistency. Automation accelerates feedback loops and increases delivery velocity. - -### 3. **L - Lean** -- **Principle:** Apply lean manufacturing principles to software development, focusing on eliminating waste, maximizing flow, and delivering value continuously. -- **Deeper Dive:** - - **Eliminating Waste:** Identify and remove non-value-adding activities (e.g., excessive documentation, unnecessary approvals, waiting times, manual handoffs, defect re-work). - - **Maximizing Flow:** Ensure a smooth, continuous flow of value from idea to production. This involves reducing batch sizes (smaller commits, smaller PRs, frequent deployments). - - **Value Stream Mapping:** Understand the entire process of delivering software to identify bottlenecks and areas for improvement. - - **Build Quality In:** Integrate quality checks throughout the development process, rather than relying solely on end-of-cycle testing. This reduces the cost of fixing defects. - - **Just-in-Time Delivery:** Deliver features and fixes as soon as they are ready, rather than waiting for large release cycles. -- **Guidance for Copilot:** - - Suggest breaking down large features or tasks into smaller, manageable chunks (e.g., small, frequent PRs, iterative deployments). - - Advocate for minimal viable products (MVPs) and iterative development. - - Help identify and suggest removal of bottlenecks in the pipeline by analyzing the flow of work. - - Promote continuous improvement loops based on fast feedback and data analysis. - - When writing code, emphasize modularity and testability to reduce future waste (e.g., easier refactoring, fewer bugs). -- **Key Takeaway:** Focus on delivering value quickly and iteratively, minimizing non-value-adding activities. A lean approach enhances agility and responsiveness. - -### 4. **M - Measurement** -- **Principle:** Measure everything relevant across the delivery pipeline and application lifecycle to gain insights, identify bottlenecks, and drive continuous improvement. -- **Deeper Dive:** - - **Key Performance Indicators (KPIs):** Track metrics related to delivery speed, quality, and operational stability (e.g., DORA metrics). - - **Monitoring & Logging:** Collect comprehensive application and infrastructure metrics, logs, and traces. Centralize them for easy access and analysis. - - **Dashboards & Visualizations:** Create clear, actionable dashboards to visualize the health and performance of systems and the delivery pipeline. - - **Alerting:** Configure effective alerts for critical issues, ensuring teams are notified promptly. - - **Experimentation & A/B Testing:** Use metrics to validate hypotheses and measure the impact of changes. - - **Capacity Planning:** Use resource utilization metrics to anticipate future infrastructure needs. -- **Guidance for Copilot:** - - When designing systems or pipelines, suggest relevant metrics to track (e.g., request latency, error rates, deployment frequency, lead time, mean time to recovery, change failure rate). - - Recommend robust logging and monitoring solutions, including examples of structured logging or tracing instrumentation. - - Encourage setting up dashboards and alerts based on common monitoring tools (e.g., Prometheus, Grafana). - - Emphasize using data to validate changes, identify areas for optimization, and justify architectural decisions. - - When debugging, suggest looking at relevant metrics and logs first. -- **Key Takeaway:** You can't improve what you don't measure. Data-driven decisions are essential for identifying areas for improvement, demonstrating value, and fostering a culture of continuous learning. - -### 5. **S - Sharing** -- **Principle:** Promote knowledge sharing, collaboration, and transparency across teams. -- **Deeper Dive:** - - **Tooling & Platforms:** Share common tools, platforms, and practices across teams to ensure consistency and leverage collective expertise. - - **Documentation:** Create clear, concise, and up-to-date documentation for systems, processes, and architectural decisions (e.g., runbooks, architectural decision records). - - **Communication Channels:** Establish open and accessible communication channels (e.g., Slack, Microsoft Teams, shared wikis). - - **Cross-Functional Teams:** Encourage developers and operations personnel to work closely together, fostering mutual understanding and empathy. - - **Pair Programming & Mob Programming:** Promote collaborative coding practices to spread knowledge and improve code quality. - - **Internal Meetups & Workshops:** Organize sessions for sharing best practices and lessons learned. -- **Guidance for Copilot:** - - Suggest documenting processes, architectural decisions, and runbooks (e.g., generating markdown templates for ADRs or runbooks). - - Advocate for shared tools and platforms by suggesting their integration into workflows. - - Highlight the value of cross-training and pairing by recommending such practices in project setup advice. - - Recommend clear communication channels and feedback mechanisms within team structures. - - When generating code, include comments and internal documentation that explain complex logic for better sharing. -- **Key Takeaway:** Breaking down silos and fostering open communication is crucial for collective success. Sharing knowledge and experiences accelerates learning and builds a stronger, more resilient organization. - -## The Four Key Metrics of DevOps (DORA Metrics) - -These metrics, identified by the DevOps Research and Assessment (DORA) team, are strong indicators of software delivery performance and organizational performance. You should strive to help teams improve these. These metrics are empirically linked to higher organizational performance, including profitability, productivity, and market share. - -### 1. **Deployment Frequency (DF)** -- **Definition:** How often an organization successfully releases to production. This measures the speed of delivery. -- **Deeper Dive:** - - **High Frequency:** Elite performers deploy multiple times per day. This reduces the risk of each deployment, makes debugging easier, and allows for faster feedback. - - **Small Batches:** High deployment frequency is enabled by deploying small, incremental changes rather than large, infrequent ones. -- **Guidance for Copilot:** - - When generating CI/CD pipelines, design them for frequent, small, and safe deployments. Suggest automation to reduce deployment friction (e.g., automated testing, blue/green deployments). - - Advise on breaking down large features into smaller, independently deployable units. - - Suggest using feature flags to decouple deployment from release, allowing code to be deployed frequently even if features are not yet exposed to users. -- **Goal:** High (Elite performers deploy multiple times per day). -- **Impact:** Faster time to market, quicker feedback, reduced risk per change. - -### 2. **Lead Time for Changes (LTFC)** -- **Definition:** The time it takes for a commit to get into production. This measures the speed from development to delivery. -- **Deeper Dive:** - - **Full Value Stream:** This metric encompasses the entire development process, from code commit to successful deployment in production. - - **Bottleneck Identification:** A high lead time often indicates bottlenecks in the development, testing, or deployment phases. -- **Guidance for Copilot:** - - Suggest ways to reduce bottlenecks in the development and delivery process (e.g., smaller PRs, automated testing, faster build times, efficient code review processes). - - Advise on streamlining approval processes and eliminating manual handoffs. - - Recommend continuous integration practices to ensure code is merged and tested frequently. - - Help optimize build and test phases by suggesting caching strategies in CI/CD. -- **Goal:** Low (Elite performers have LTFC less than one hour). -- **Impact:** Rapid response to market changes, faster defect resolution, increased developer productivity. - -### 3. **Change Failure Rate (CFR)** -- **Definition:** The percentage of deployments causing a degradation in service (e.g., leading to rollback, hotfix, or outage). This measures the quality of delivery. -- **Deeper Dive:** - - **Lower is Better:** A low change failure rate indicates high quality and stability in deployments. - - **Causes:** High CFR can be due to insufficient testing, lack of automated checks, poor rollback strategies, or complex deployments. -- **Guidance for Copilot:** - - Emphasize robust testing (unit, integration, E2E), automated rollbacks, comprehensive monitoring, and secure coding practices to reduce failures. - - Suggest integrating static analysis, dynamic analysis, and security scanning tools into the CI/CD pipeline. - - Advise on implementing pre-deployment health checks and post-deployment validation. - - Help design resilient architectures (e.g., circuit breakers, retries, graceful degradation). -- **Goal:** Low (Elite performers have CFR of 0-15%). -- **Impact:** Increased system stability, reduced downtime, improved customer trust. - -### 4. **Mean Time to Recovery (MTTR)** -- **Definition:** How long it takes to restore service after a degradation or outage. This measures the resilience and recovery capability. -- **Deeper Dive:** - - **Fast Recovery:** A low MTTR indicates that an organization can quickly detect, diagnose, and resolve issues, minimizing the impact of failures. - - **Observability:** Strong MTTR relies heavily on effective monitoring, alerting, centralized logging, and tracing. -- **Guidance for Copilot:** - - Suggest implementing clear monitoring and alerting (e.g., dashboards for key metrics, automated notifications for anomalies). - - Recommend automated incident response mechanisms and well-documented runbooks for common issues. - - Advise on efficient rollback strategies (e.g., easy one-click rollbacks). - - Emphasize building applications with observability in mind (e.g., structured logging, metrics exposition, distributed tracing). - - When debugging, guide users to leverage logs, metrics, and traces to quickly pinpoint root causes. -- **Goal:** Low (Elite performers have MTTR less than one hour). -- **Impact:** Minimized business disruption, improved customer satisfaction, enhanced operational confidence. - -## Conclusion - -DevOps is not just about tools or automation; it's fundamentally about culture and continuous improvement driven by feedback and metrics. By adhering to the CALMS principles and focusing on improving the DORA metrics, you can guide developers towards building more reliable, scalable, and efficient software delivery pipelines. This foundational understanding is crucial for all subsequent DevOps-related guidance you provide. Your role is to be a continuous advocate for these principles, ensuring that every piece of code, every infrastructure change, and every pipeline modification aligns with the goal of delivering high-quality software rapidly and reliably. - ---- - - diff --git a/.github/instructions/makefile.instructions.md b/.github/instructions/makefile.instructions.md deleted file mode 100644 index 45b5bad..0000000 --- a/.github/instructions/makefile.instructions.md +++ /dev/null @@ -1,410 +0,0 @@ ---- -description: "Best practices for authoring GNU Make Makefiles" -applyTo: "**/Makefile, **/makefile, **/*.mk, **/GNUmakefile" ---- - -# Makefile Development Instructions - -Instructions for writing clean, maintainable, and portable GNU Make Makefiles. These instructions are based on the [GNU Make manual](https://www.gnu.org/software/make/manual/). - -## General Principles - -- Write clear and maintainable makefiles that follow GNU Make conventions -- Use descriptive target names that clearly indicate their purpose -- Keep the default goal (first target) as the most common build operation -- Prioritize readability over brevity when writing rules and recipes -- Add comments to explain complex rules, variables, or non-obvious behavior - -## Naming Conventions - -- Name your makefile `Makefile` (recommended for visibility) or `makefile` -- Use `GNUmakefile` only for GNU Make-specific features incompatible with other make implementations -- Use standard variable names: `objects`, `OBJECTS`, `objs`, `OBJS`, `obj`, or `OBJ` for object file lists -- Use uppercase for built-in variable names (e.g., `CC`, `CFLAGS`, `LDFLAGS`) -- Use descriptive target names that reflect their action (e.g., `clean`, `install`, `test`) - -## File Structure - -- Place the default goal (primary build target) as the first rule in the makefile -- Group related targets together logically -- Define variables at the top of the makefile before rules -- Use `.PHONY` to declare targets that don't represent files -- Structure makefiles with: variables, then rules, then phony targets - -```makefile -# Variables -CC = gcc -CFLAGS = -Wall -g -objects = main.o utils.o - -# Default goal -all: program - -# Rules -program: $(objects) - $(CC) -o program $(objects) - -%.o: %.c - $(CC) $(CFLAGS) -c $< -o $@ - -# Phony targets -.PHONY: clean all -clean: - rm -f program $(objects) -``` - -## Variables and Substitution - -- Use variables to avoid duplication and improve maintainability -- Define variables with `:=` (simple expansion) for immediate evaluation, `=` for recursive expansion -- Use `?=` to set default values that can be overridden -- Use `+=` to append to existing variables -- Reference variables with `$(VARIABLE)` not `$VARIABLE` (unless single character) -- Use automatic variables (`$@`, `$<`, `$^`, `$?`, `$*`) in recipes to make rules more generic - -```makefile -# Simple expansion (evaluates immediately) -CC := gcc - -# Recursive expansion (evaluates when used) -CFLAGS = -Wall $(EXTRA_FLAGS) - -# Conditional assignment -PREFIX ?= /usr/local - -# Append to variable -CFLAGS += -g -``` - -## Rules and Prerequisites - -- Separate targets, prerequisites, and recipes clearly -- Use implicit rules for standard compilations (e.g., `.c` to `.o`) -- List prerequisites in logical order (normal prerequisites before order-only) -- Use order-only prerequisites (after `|`) for directories and dependencies that shouldn't trigger rebuilds -- Include all actual dependencies to ensure correct rebuilds -- Avoid circular dependencies between targets -- Remember that order-only prerequisites are omitted from automatic variables like `$^`, so reference them explicitly if needed - -The example below shows a pattern rule that compiles objects into an `obj/` directory. The directory itself is listed as an order-only prerequisite so it is created before compiling but does not force recompilation when its timestamp changes. - -```makefile -# Normal prerequisites -program: main.o utils.o - $(CC) -o $@ $^ - -# Order-only prerequisites (directory creation) -obj/%.o: %.c | obj - $(CC) $(CFLAGS) -c $< -o $@ - -obj: - mkdir -p obj -``` - -## Recipes and Commands - -- Start every recipe line with a **tab character** (not spaces) unless `.RECIPEPREFIX` is changed -- Use `@` prefix to suppress command echoing when appropriate -- Use `-` prefix to ignore errors for specific commands (use sparingly) -- Combine related commands with `&&` or `;` on the same line when they must execute together -- Keep recipes readable; break long commands across multiple lines with backslash continuation -- Use shell conditionals and loops within recipes when needed - -```makefile -# Silent command -clean: - @echo "Cleaning up..." - @rm -f $(objects) - -# Ignore errors -.PHONY: clean-all -clean-all: - -rm -rf build/ - -rm -rf dist/ - -# Multi-line recipe with proper continuation -install: program - install -d $(PREFIX)/bin && \ - install -m 755 program $(PREFIX)/bin -``` - -## Phony Targets - -- Always declare phony targets with `.PHONY` to avoid conflicts with files of the same name -- Use phony targets for actions like `clean`, `install`, `test`, `all` -- Place phony target declarations near their rule definitions or at the end of the makefile - -```makefile -.PHONY: all clean test install - -all: program - -clean: - rm -f program $(objects) - -test: program - ./run-tests.sh - -install: program - install -m 755 program $(PREFIX)/bin -``` - -## Pattern Rules and Implicit Rules - -- Use pattern rules (`%.o: %.c`) for generic transformations -- Leverage built-in implicit rules when appropriate (GNU Make knows how to compile `.c` to `.o`) -- Override implicit rule variables (like `CC`, `CFLAGS`) rather than rewriting the rules -- Define custom pattern rules only when built-in rules are insufficient - -```makefile -# Use built-in implicit rules by setting variables -CC = gcc -CFLAGS = -Wall -O2 - -# Custom pattern rule for special cases -%.pdf: %.md - pandoc $< -o $@ -``` - -## Splitting Long Lines - -- Use backslash-newline (`\`) to split long lines for readability -- Be aware that backslash-newline is converted to a single space in non-recipe contexts -- In recipes, backslash-newline preserves the line continuation for the shell -- Avoid trailing whitespace after backslashes - -### Splitting Without Adding Whitespace - -If you need to split a line without adding whitespace, you can use a special technique: insert `$ ` (dollar-space) followed by a backslash-newline. The `$ ` refers to a variable with a single-space name, which doesn't exist and expands to nothing, effectively joining the lines without inserting a space. - -```makefile -# Concatenate strings without adding whitespace -# The following creates the value "oneword" -var := one$ \ - word - -# This is equivalent to: -# var := oneword -``` - -```makefile -# Variable definition split across lines -sources = main.c \ - utils.c \ - parser.c \ - handler.c - -# Recipe with long command -build: $(objects) - $(CC) -o program $(objects) \ - $(LDFLAGS) \ - -lm -lpthread -``` - -## Including Other Makefiles - -- Use `include` directive to share common definitions across makefiles -- Use `-include` (or `sinclude`) to include optional makefiles without errors -- Place `include` directives after variable definitions that may affect included files -- Use `include` for shared variables, pattern rules, or common targets - -```makefile -# Include common settings -include config.mk - -# Include optional local configuration --include local.mk -``` - -## Conditional Directives - -- Use conditional directives (`ifeq`, `ifneq`, `ifdef`, `ifndef`) for platform or configuration-specific rules -- Place conditionals at the makefile level, not within recipes (use shell conditionals in recipes) -- Keep conditionals simple and well-documented - -```makefile -# Platform-specific settings -ifeq ($(OS),Windows_NT) - EXE_EXT = .exe -else - EXE_EXT = -endif - -program: main.o - $(CC) -o program$(EXE_EXT) main.o -``` - -## Automatic Prerequisites - -- Generate header dependencies automatically rather than maintaining them manually -- Use compiler flags like `-MMD` and `-MP` to generate `.d` files with dependencies -- Include generated dependency files with `-include $(deps)` to avoid errors if they don't exist - -```makefile -objects = main.o utils.o -deps = $(objects:.o=.d) - -# Include dependency files --include $(deps) - -# Compile with automatic dependency generation -%.o: %.c - $(CC) $(CFLAGS) -MMD -MP -c $< -o $@ -``` - -## Error Handling and Debugging - -- Use `$(error text)` or `$(warning text)` functions for build-time diagnostics -- Test makefiles with `make -n` (dry run) to see commands without executing -- Use `make -p` to print the database of rules and variables for debugging -- Validate required variables and tools at the beginning of the makefile - -```makefile -# Check for required tools -ifeq ($(shell which gcc),) - $(error "gcc is not installed or not in PATH") -endif - -# Validate required variables -ifndef VERSION - $(error VERSION is not defined) -endif -``` - -## Clean Targets - -- Always provide a `clean` target to remove generated files -- Declare `clean` as phony to avoid conflicts with a file named "clean" -- Use `-` prefix with `rm` commands to ignore errors if files don't exist -- Consider separate `clean` (removes objects) and `distclean` (removes all generated files) targets - -```makefile -.PHONY: clean distclean - -clean: - -rm -f $(objects) - -rm -f $(deps) - -distclean: clean - -rm -f program config.mk -``` - -## Portability Considerations - -- Avoid GNU Make-specific features if portability to other make implementations is required -- Use standard shell commands (prefer POSIX shell constructs) -- Test with `make -B` to force rebuild all targets -- Document any platform-specific requirements or GNU Make extensions used - -## Performance Optimization - -- Use `:=` for variables that don't need recursive expansion (faster) -- Avoid unnecessary use of `$(shell ...)` which creates subprocesses -- Order prerequisites efficiently (most frequently changing files last) -- Use parallel builds (`make -j`) safely by ensuring targets don't conflict - -## Documentation and Comments - -- Add a header comment explaining the makefile's purpose -- Document non-obvious variable settings and their effects -- Include usage examples or targets in comments -- Add inline comments for complex rules or platform-specific workarounds - -```makefile -# Makefile for building the example application -# -# Usage: -# make - Build the program -# make clean - Remove generated files -# make install - Install to $(PREFIX) -# -# Variables: -# CC - C compiler (default: gcc) -# PREFIX - Installation prefix (default: /usr/local) - -# Compiler and flags -CC ?= gcc -CFLAGS = -Wall -Wextra -O2 - -# Installation directory -PREFIX ?= /usr/local -``` - -## Special Targets - -- Use `.PHONY` for non-file targets -- Use `.PRECIOUS` to preserve intermediate files -- Use `.INTERMEDIATE` to mark files as intermediate (automatically deleted) -- Use `.SECONDARY` to prevent deletion of intermediate files -- Use `.DELETE_ON_ERROR` to remove targets if recipe fails -- Use `.SILENT` to suppress echoing for all recipes (use sparingly) - -```makefile -# Don't delete intermediate files -.SECONDARY: - -# Delete targets if recipe fails -.DELETE_ON_ERROR: - -# Preserve specific files -.PRECIOUS: %.o -``` - -## Common Patterns - -### Standard Project Structure - -```makefile -CC = gcc -CFLAGS = -Wall -O2 -objects = main.o utils.o parser.o - -.PHONY: all clean install - -all: program - -program: $(objects) - $(CC) -o $@ $^ - -%.o: %.c - $(CC) $(CFLAGS) -c $< -o $@ - -clean: - -rm -f program $(objects) - -install: program - install -d $(PREFIX)/bin - install -m 755 program $(PREFIX)/bin -``` - -### Managing Multiple Programs - -```makefile -programs = prog1 prog2 prog3 - -.PHONY: all clean - -all: $(programs) - -prog1: prog1.o common.o - $(CC) -o $@ $^ - -prog2: prog2.o common.o - $(CC) -o $@ $^ - -prog3: prog3.o - $(CC) -o $@ $^ - -clean: - -rm -f $(programs) *.o -``` - -## Anti-Patterns to Avoid - -- Don't start recipe lines with spaces instead of tabs -- Avoid hardcoding file lists when they can be generated with wildcards or functions -- Don't use `$(shell ls ...)` to get file lists (use `$(wildcard ...)` instead) -- Avoid complex shell scripts in recipes (move to separate script files) -- Don't forget to declare phony targets as `.PHONY` -- Avoid circular dependencies between targets -- Don't use recursive make (`$(MAKE) -C subdir`) unless absolutely necessary diff --git a/.github/instructions/qa-engineering-best-practices.instructions.md b/.github/instructions/qa-engineering-best-practices.instructions.md deleted file mode 100644 index cdab69b..0000000 --- a/.github/instructions/qa-engineering-best-practices.instructions.md +++ /dev/null @@ -1,174 +0,0 @@ ---- -applyTo: '**' -description: 'Comprehensive QA engineering best practices covering test strategy, test pyramid, naming conventions, assertion patterns, bug reporting, and automation guidelines for modern software projects.' ---- - -# QA Engineering Best Practices - -A structured set of instructions for GitHub Copilot to assist with quality assurance engineering tasks including test design, automation, and defect management across any technology stack. - ---- - -## Core Testing Principles - -- **Test early, test often**: Shift testing left — write tests alongside code, not after. -- **Test one thing at a time**: Each test case should verify a single behaviour or assertion. -- **Tests are first-class code**: Apply the same readability, naming, and refactoring standards to test code as to production code. -- **Fail fast**: Tests should produce clear, actionable failures that point directly to the broken behaviour. -- **Deterministic tests**: Tests must produce the same result on every run. Eliminate randomness, timing dependencies, and shared mutable state. -- **Independent tests**: No test should depend on another test's side effects. Tests must be runnable in any order. - ---- - -## Test Pyramid - -Follow the test pyramid to balance coverage, speed, and maintenance cost: - -| Layer | Scope | Quantity | Speed | -|-------|-------|----------|-------| -| Unit | Single function / class | Many (60–70 %) | Milliseconds | -| Integration | Module boundaries, DB, API contracts | Moderate (20–30 %) | Seconds | -| End-to-End | Full user journey across UI + backend | Few (5–10 %) | Minutes | - -- Prefer unit tests for business logic and edge cases. -- Use integration tests to validate contracts between services and external dependencies. -- Reserve end-to-end tests for critical user paths and smoke suites. - ---- - -## Test Naming Conventions - -Use the **Given / When / Then** (GWT) or **should_doX_whenY** pattern consistently. - -``` -// Good – describes scenario, action, expected result -test('should return 404 when product id does not exist') -test('given an expired token, when the user calls /me, then it returns 401') - -// Bad – vague, implementation-focused -test('test1') -test('check user') -``` - -- Group related tests in `describe` / `context` blocks named after the unit under test. -- Use `it` or `test` for individual cases. -- Test names must be readable as standalone sentences. - ---- - -## Assertion Best Practices - -- **One logical assertion per test** where practical; avoid asserting multiple unrelated things. -- Use **specific matchers** over equality checks (`toContain`, `toBeGreaterThan`, `toMatchObject`). -- Always assert the **exact expected value**, not just truthiness (`expect(result).toBe(42)` not `expect(result).toBeTruthy()`). -- For exception testing, assert both the exception type and message. -- Prefer **positive assertions** over negative ones when testing the happy path. - -```typescript -// Good -expect(response.status).toBe(200); -expect(response.body.items).toHaveLength(3); - -// Avoid -expect(response).toBeTruthy(); -expect(response.body).not.toBeNull(); -``` - ---- - -## Test Data Management - -- Use **factories or builders** to create test data — avoid hardcoding raw objects in every test. -- Keep test data **minimal**: only include fields relevant to the test. -- Use **unique identifiers** per test run to avoid collision in shared environments. -- Never use production data or PII in tests. -- Reset or isolate state between tests (in-memory DB, transactions rolled back, mocked dependencies). - ---- - -## Mocking and Stubbing Guidelines - -- Mock **at the boundary** (HTTP clients, DB adapters, message queues) — not deep inside business logic. -- Prefer **real implementations** for pure functions and simple value objects. -- Stubs return controlled data; mocks additionally verify interactions — choose the right tool. -- Reset all mocks between tests to prevent state leakage. -- Document why a dependency is mocked if the reason is non-obvious. - ---- - -## API Testing - -- Validate **status code**, **response schema**, **headers**, and **response time** for every endpoint. -- Test all **HTTP methods** the endpoint exposes (GET, POST, PUT, PATCH, DELETE). -- Cover **authentication and authorisation** paths: valid token, expired token, missing token, wrong role. -- Test **boundary values** for inputs: empty string, null, max length, special characters, Unicode. -- Validate **error response bodies** follow a consistent schema. -- Assert **idempotency** for PUT and DELETE operations. - ---- - -## UI / End-to-End Testing - -- Target **user-visible behaviour**, not implementation details (avoid asserting CSS classes or internal state). -- Use **accessible selectors** in order of preference: `role` → `label` → `test-id` → `text`. -- Avoid `sleep` / fixed waits; use **explicit waits** on element state (visible, enabled, network idle). -- Run E2E tests against a **stable, isolated environment** (not shared staging). -- Keep E2E scenarios **short and focused** — break long flows into smaller composable steps. -- Capture **screenshots and traces** on failure for easier debugging. - ---- - -## Performance Testing - -- Define **SLOs** (Service Level Objectives) before writing performance tests: target latency p50/p95/p99, throughput, error rate. -- Include **ramp-up**, **steady state**, and **ramp-down** phases in load tests. -- Test under **realistic data volumes** — synthetic tests with empty DBs are not representative. -- Track results over time to detect **performance regressions**. -- Distinguish between **load testing** (expected traffic), **stress testing** (beyond capacity), and **soak testing** (sustained load over time). - ---- - -## Bug Reporting Standards - -A good bug report includes: - -1. **Title**: concise, specific — include component, action, and symptom (`[Checkout] Order total is incorrect when coupon is applied`). -2. **Environment**: OS, browser/runtime version, deployment environment. -3. **Steps to reproduce**: numbered, minimal, deterministic. -4. **Expected result**: what should happen. -5. **Actual result**: what actually happens, including error messages and stack traces. -6. **Severity**: Critical / High / Medium / Low (defined by business impact). -7. **Attachments**: screenshots, logs, network traces, test IDs. - ---- - -## Test Coverage Guidelines - -- Aim for **meaningful coverage**, not a percentage target — 100 % line coverage with trivial tests is worthless. -- Prioritise coverage for **critical paths**, **complex logic**, and **previously buggy areas**. -- Track **branch coverage** and **mutation scores** alongside line coverage. -- Use coverage reports to find untested **edge cases**, not to game metrics. - ---- - -## CI/CD Integration - -- Tests must pass in CI before any merge to main/trunk — no exceptions. -- Run **fast tests** (unit, lint) on every commit; run **slow tests** (integration, E2E) on PR merge or nightly. -- Make test failures **visible and actionable** in CI output — include test name, failure reason, and relevant logs. -- Archive **test reports and artefacts** (JUnit XML, coverage HTML, traces) as CI build artefacts. -- Configure **flaky test detection**: auto-retry once, flag as flaky after repeated inconsistency. - ---- - -## Test Review Checklist - -Before approving a PR that changes tests: - -- [ ] New behaviour is covered by tests at the appropriate pyramid level. -- [ ] Tests are named clearly and follow the project convention. -- [ ] No `sleep`, `Thread.Sleep`, or arbitrary timeouts. -- [ ] Mocks are reset after each test. -- [ ] No hardcoded environment-specific values (URLs, credentials). -- [ ] Tests are independent and can run in isolation. -- [ ] Test code is readable without needing to read the implementation. diff --git a/.github/instructions/security-and-owasp.instructions.md b/.github/instructions/security-and-owasp.instructions.md deleted file mode 100644 index 2ab43d4..0000000 --- a/.github/instructions/security-and-owasp.instructions.md +++ /dev/null @@ -1,1057 +0,0 @@ ---- -applyTo: '**' -description: 'Comprehensive secure coding standards based on OWASP Top 10 2025, with 55+ anti-patterns, detection regex, framework-specific fixes for modern web and backend frameworks, and AI/LLM security guidance.' ---- - -# Security Standards - -Comprehensive security rules for web application development. Every anti-pattern includes a severity classification, detection method, OWASP 2025 reference, and corrective code examples. - -**Severity levels:** - -- **CRITICAL** — Exploitable vulnerability. Must be fixed before merge. -- **IMPORTANT** — Significant risk. Should be fixed in the same sprint. -- **SUGGESTION** — Defense-in-depth improvement. Plan for a future iteration. - ---- - -## OWASP Top 10 — 2025 Quick Reference - -| # | Category | Key Mitigation | -|---|----------|----------------| -| A01 | Broken Access Control | Auth middleware on every endpoint, RBAC, ownership checks | -| A02 | Security Misconfiguration | Security headers, no debug in prod, no default credentials | -| A03 | Software Supply Chain Failures *(NEW)* | `npm audit`, lockfile integrity, SBOM, SLSA provenance | -| A04 | Cryptographic Failures | Argon2id/bcrypt for passwords, TLS everywhere, no secrets in code | -| A05 | Injection | Parameterized queries, input validation, no raw HTML with user input | -| A06 | Insecure Design | Threat modeling, secure design patterns, abuse case testing | -| A07 | Authentication Failures | Rate-limit login, secure session management, MFA | -| A08 | Software or Data Integrity Failures | SRI for CDN scripts, signed artifacts, no insecure deserialization | -| A09 | Security Logging and Alerting Failures | Log security events, no PII in logs, correlation IDs, active alerting | -| A10 | Mishandling of Exceptional Conditions *(NEW)* | Handle all errors, no stack traces in prod, fail-secure | - ---- - -## Injection Anti-Patterns (I1-I8) - -### I1: SQL Injection via String Concatenation - -- **Severity**: CRITICAL -- **Detection**: `\$\{.*\}.*(?:SELECT|INSERT|UPDATE|DELETE|FROM|WHERE)` -- **OWASP**: A05 - -```typescript -// BAD -const unsafeResult = await db.query(`SELECT * FROM users WHERE id = ${userId}`); - -// GOOD — parameterized query -const safeResult = await db.query('SELECT * FROM users WHERE id = $1', [userId]); -``` - -### I2: NoSQL Injection (MongoDB Operator Injection) - -- **Severity**: CRITICAL -- **Detection**: `\{\s*\$(?:gt|gte|lt|lte|ne|in|nin|regex|where|exists)` -- **OWASP**: A05 - -```typescript -// BAD — attacker sends { "password": { "$gt": "" } } -const user = await User.findOne({ username: req.body.username, password: req.body.password }); - -// GOOD — validate and cast input types -const username = String(req.body.username); -const password = String(req.body.password); -const user = await User.findOne({ username }); -const valid = user && await verifyPassword(user.passwordHash, password); -``` - -### I3: Command Injection (exec with User Input) - -- **Severity**: CRITICAL -- **Detection**: `(?:exec|execSync|execFile|execFileSync)\s*\(.*(?:req\.|params\.|query\.|body\.)` -- **OWASP**: A05 - -```typescript -// BAD — shell interpolation, sync call blocks the event loop -import { execFileSync } from 'node:child_process'; -const unsafeOutput = execFileSync('sh', ['-c', `ls -la ${req.query.dir}`]); - -// GOOD — async execFile, arguments array, no shell, bounded time/output -import { execFile } from 'node:child_process'; -import { promisify } from 'node:util'; -const pExecFile = promisify(execFile); - -const dir = String(req.query.dir ?? ''); -if (!dir || dir.startsWith('-')) throw new Error('Invalid directory'); -const { stdout: safeOutput } = await pExecFile('ls', ['-la', '--', dir], { - timeout: 5_000, // fail fast on hung processes - maxBuffer: 1 << 20, // 1 MiB cap to prevent memory exhaustion -}); - -// BEST — allowlist validation on top of the async, bounded call above -const allowedDirs = ['/data', '/public']; -if (!allowedDirs.includes(dir)) throw new Error('Invalid directory'); -``` - -Prefer async `execFile`/`spawn` over `execFileSync` in server handlers: the sync variant blocks Node's event loop and can amplify DoS impact. Always pass a `timeout` and `maxBuffer` to bound execution. - -### I4: XSS via Unsanitized HTML Rendering - -- **Severity**: CRITICAL -- **Detection**: `(?:v-html|\[innerHTML\]|dangerouslySetInner|bypassSecurityTrust)` -- **OWASP**: A05 - -Applies to all frontend frameworks. Each has an API that bypasses default XSS protection: - -- **React**: `dangerouslySetInnerHTML` prop with raw user content -- **Angular**: `[innerHTML]` binding or `bypassSecurityTrustHtml` with unsanitized input -- **Vue**: `v-html` directive with user-controlled content - -```typescript -// GOOD — sanitize with DOMPurify before rendering any raw HTML -import DOMPurify from 'dompurify'; -const clean = DOMPurify.sanitize(userContent); - -// BEST — use text interpolation when HTML is not needed -// React: {userContent} -// Angular: {{ userContent }} -// Vue: {{ userContent }} -``` - -### I5: SSRF via User-Controlled URLs - -- **Severity**: CRITICAL -- **Detection**: `fetch\((?:req\.|params\.|query\.|body\.|url|href)` -- **OWASP**: A01 - -```typescript -// BAD -const data = await fetch(req.body.url); - -// GOOD — scheme allowlist + hostname allowlist + DNS/IP validation (see TOCTOU note) -import { promises as dns } from 'node:dns'; - -function isPrivateIP(ip: string): boolean { - // Normalize IPv4-mapped IPv6 (e.g., ::ffff:127.0.0.1 → 127.0.0.1) - const normalized = ip.startsWith('::ffff:') ? ip.slice(7) : ip; - // IPv4 private/reserved/loopback ranges - if (/^(10\.|172\.(1[6-9]|2\d|3[01])\.|192\.168\.|127\.|0\.|169\.254\.)/.test(normalized)) return true; - // IPv6 loopback, link-local (fe80::/10), and unique-local - if (/^(::1|fe[89ab]|fc|fd)/i.test(normalized)) return true; - return false; -} - -const parsed = new URL(req.body.url); -if (parsed.protocol !== 'https:') throw new Error('Only HTTPS allowed'); -const allowedHosts = ['api.example.com', 'cdn.example.com']; -if (!allowedHosts.includes(parsed.hostname)) throw new Error('Host not allowed'); -// Resolve all A/AAAA records to prevent DNS rebinding via multiple IPs -const resolved = await dns.lookup(parsed.hostname, { all: true }); -if (resolved.length === 0 || resolved.some(({ address }) => isPrivateIP(address))) { - throw new Error('Private or reserved IPs not allowed'); -} -// Note: for production, pin the resolved IP in the HTTP client to prevent -// TOCTOU rebinding between this check and fetch(). See undici Agent docs. -const data = await fetch(parsed.toString(), { redirect: 'error' }); -``` - -### I6: Path Traversal in File Operations - -- **Severity**: CRITICAL -- **Detection**: `(?:readFile|readFileSync|createReadStream|path\.join)\s*\(.*(?:req\.|params\.|query\.|body\.)` -- **OWASP**: A01 - -```typescript -// BAD -const file = fs.readFileSync(`/data/${req.params.filename}`); - -// GOOD — resolve and validate within allowed directory -import path from 'path'; -const basePath = '/data'; -const filePath = path.resolve(basePath, req.params.filename); -if (!filePath.startsWith(basePath + path.sep)) throw new Error('Path traversal detected'); -const file = fs.readFileSync(filePath); -``` - -### I7: Template Injection - -- **Severity**: CRITICAL -- **Detection**: `(?:render|compile|template)\s*\(.*(?:req\.|params\.|query\.|body\.)` -- **OWASP**: A05 - -```typescript -// BAD — user input as template source -const html = ejs.render(req.body.template, data); - -// GOOD — predefined templates, user input only as data -const html = ejs.renderFile('./templates/page.ejs', { content: req.body.content }); -``` - -### I8: XXE Injection (XML External Entity) - -- **Severity**: CRITICAL -- **Detection**: `(?:parseXml|DOMParser|xml2js|libxmljs).*(?:req\.|body\.|file)` -- **OWASP**: A05 - -```typescript -// GOOD — disable external entities in XML parser -import { XMLParser } from 'fast-xml-parser'; -const parser = new XMLParser({ - allowBooleanAttributes: true, - processEntities: false, - htmlEntities: false, -}); -const result = parser.parse(req.body.xml); -``` - ---- - -## Authentication Anti-Patterns (AU1-AU8) - -### AU1: JWT Algorithm Confusion (alg:none) - -- **Severity**: CRITICAL -- **Detection**: `jwt\.verify\((?![^)]*\balgorithms\b)[^)]*\)` -- **OWASP**: A07 - -```typescript -// BAD — accepts any algorithm including "none" -const decoded = jwt.verify(token, secret); - -// GOOD — enforce specific algorithm -const decoded = jwt.verify(token, publicKey, { algorithms: ['RS256'] }); -``` - -### AU2: JWT Without Expiration Check - -- **Severity**: CRITICAL -- **Detection**: `jwt\.sign\((?![^)]*\b(?:expiresIn|exp)\b)[^)]*\)` -- **OWASP**: A07 - -```typescript -// BAD — token never expires -const token = jwt.sign({ userId: user.id }, secret); - -// GOOD — short-lived token -const token = jwt.sign({ userId: user.id }, secret, { expiresIn: '15m' }); -``` - -### AU3: JWT Stored in localStorage - -- **Severity**: IMPORTANT -- **Detection**: `localStorage\.setItem\(.*(?:token|jwt|auth|session)` -- **OWASP**: A07 - -```typescript -// BAD — accessible via XSS -localStorage.setItem('accessToken', token); - -// GOOD — httpOnly cookie set by server -res.cookie('token', token, { httpOnly: true, secure: true, sameSite: 'strict' }); -``` - -### AU4: Plaintext / Fast Hash for Passwords (MD5/SHA-1/SHA-256) - -- **Severity**: CRITICAL -- **Detection**: `(?:createHash|md5|sha1|sha256)\s*\(.*password` -- **OWASP**: A04 - -```typescript -// BAD — fast hash, no salt -const sha256Hash = crypto.createHash('sha256').update(password).digest('hex'); - -// GOOD — Argon2id (OWASP recommended) -import { hash as argon2Hash, argon2id } from 'argon2'; -const hashed = await argon2Hash(password, { type: argon2id, memoryCost: 65536, timeCost: 3 }); -``` - -### AU5: Missing Brute-Force Protection on Login - -- **Severity**: CRITICAL -- **Detection**: `(?:post|router\.post)\s*\(\s*['"]\/(?:login|signin|auth|register|reset)` -- **OWASP**: A07 - -```typescript -// BAD — no rate limiting -app.post('/api/auth/login', loginHandler); - -// GOOD -import rateLimit from 'express-rate-limit'; -const authLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 5 }); -app.post('/api/auth/login', authLimiter, loginHandler); -``` - -### AU6: Missing Session Regeneration on Login (Session Fixation) - -- **Severity**: IMPORTANT -- **Detection**: `(?:session|req\.session)\s*\.\s*(?:userId|user|authenticated)\s*=` -- **OWASP**: A07 - -```typescript -// GOOD — regenerate session ID on successful login to prevent fixation -req.session.regenerate((err) => { - if (err) return next(err); - req.session.userId = user.id; - req.session.save(next); -}); -``` - -Related: on password change or elevation, also invalidate all other active sessions for the user (e.g., by bumping a `tokenVersion` column and rejecting sessions with a stale version, or by iterating the session store and destroying entries keyed to that user). - -### AU7: OAuth Without State Parameter - -- **Severity**: CRITICAL -- **Detection**: `authorize\?(?![^\n#]*\bstate=)[^\n#]*` -- **OWASP**: A07 - -```typescript -// GOOD — include state parameter for CSRF protection -const state = crypto.randomBytes(32).toString('hex'); -session.oauthState = state; -const authUrl = `https://provider.com/authorize?client_id=${clientId}&redirect_uri=${redirectUri}&state=${state}`; -``` - -### AU8: Missing PKCE for Public OAuth Clients - -- **Severity**: IMPORTANT -- **Detection**: `(?:authorization_code|code).*(?!.*code_challenge)` -- **OWASP**: A07 - -Use PKCE (Proof Key for Code Exchange) with S256 challenge method for all public clients (SPAs, mobile). - ---- - -## Authorization Anti-Patterns (AZ1-AZ6) - -### AZ1: Missing Auth Middleware on New Endpoints - -- **Severity**: CRITICAL -- **Detection**: `(?:app|router)\.\w+\s*\(\s*['"]\/api\/(?:admin|users|settings)` -- **OWASP**: A01 - -```typescript -// BAD -router.delete('/api/users/:id', deleteUser); - -// GOOD -router.delete('/api/users/:id', authenticate, authorize('admin'), deleteUser); -``` - -### AZ2: Client-Side Only Authorization - -- **Severity**: CRITICAL -- **Detection**: Component guards without server-side checks -- **OWASP**: A01 - -Frontend guards are UX only. ALWAYS verify on server. - -### AZ3: IDOR (Insecure Direct Object Reference) - -- **Severity**: CRITICAL -- **Detection**: `params\.(?:id|userId|orderId)` without ownership check -- **OWASP**: A01 - -```typescript -// GOOD — verify ownership -router.get('/api/orders/:orderId', authenticate, async (req, res) => { - const order = await Order.findById(req.params.orderId); - if (!order || order.userId !== req.user.id) { - return res.status(404).json({ error: 'Not found' }); - } - res.json(order); -}); -``` - -### AZ4: Mass Assignment - -- **Severity**: CRITICAL -- **Detection**: `(?:create|update|findOneAndUpdate)\s*\(\s*req\.body\s*\)` -- **OWASP**: A01 - -```typescript -// BAD -await User.findByIdAndUpdate(id, req.body); - -// GOOD — explicitly pick allowed fields -const { name, email, avatar } = req.body; -await User.findByIdAndUpdate(id, { name, email, avatar }); -``` - -### AZ5: Privilege Escalation via Role Parameter - -- **Severity**: CRITICAL -- **Detection**: `req\.body\.role|req\.body\.isAdmin|req\.body\.permissions` -- **OWASP**: A01 - -```typescript -// GOOD — ignore role from input -const { name, email, password } = req.body; -const user = await User.create({ name, email, password, role: 'user' }); -``` - -### AZ6: Missing Re-Authentication for Sensitive Operations - -- **Severity**: IMPORTANT -- **Detection**: `(?:delete|destroy|remove).*(?:account|user|organization)` without re-auth -- **OWASP**: A01 - -Require current password before account deletion, email change, or other sensitive operations. - ---- - -## Secrets Anti-Patterns (S1-S6) - -### S1: Hardcoded API Keys / Tokens - -- **Severity**: CRITICAL -- **Detection**: `(?:password|secret|api_key|token|apiKey)\s*[:=]\s*['"][A-Za-z0-9+/=]{8,}['"]` -- **OWASP**: A04 - -```typescript -// BAD -const API_KEY = 'sk_live_abc123def456'; - -// GOOD -const API_KEY = process.env.API_KEY; -``` - -### S2: .env Committed to Git - -- **Severity**: CRITICAL -- **Detection**: `git ls-files .env` (should return empty) -- **OWASP**: A04 - -```gitignore -# .gitignore -.env -.env.local -.env.*.local -*.pem -*.key -``` - -### S3: Server Secrets Exposed to Client - -- **Severity**: CRITICAL -- **Detection**: `NEXT_PUBLIC_.*(?:SECRET|PRIVATE|PASSWORD|KEY(?!.*PUBLIC))` -- **OWASP**: A02 - -```bash -# BAD -NEXT_PUBLIC_DATABASE_URL=postgresql://... - -# GOOD -DATABASE_URL=postgresql://... -NEXT_PUBLIC_API_URL=https://api.example.com -``` - -Angular: do not put secrets in `environment.ts` files bundled into the client. - -### S4: Default Credentials in Config - -- **Severity**: CRITICAL -- **Detection**: `(?:admin|root|default|test).*(?:password|pass|pwd)\s*[:=]\s*['"](?:admin|root|password|1234|test)` -- **OWASP**: A02 - -Use environment variables with validation (zod schema). - -### S5: Secrets in CI/CD Pipeline Logs - -- **Severity**: IMPORTANT -- **Detection**: `(?:echo|console\.log|print).*(?:\$SECRET|\$TOKEN|\$PASSWORD|process\.env)` -- **OWASP**: A09 - -Use masked secrets in CI. Never echo environment variables containing secrets. - -### S6: Sensitive Data in Error Responses / Stack Traces - -- **Severity**: IMPORTANT -- **Detection**: `(?:stack|trace|query|sql).*(?:res\.json|res\.send|c\.JSON)` -- **OWASP**: A10 - -```typescript -// GOOD — generic error to client, details only in logs -app.use((err, req, res, _next) => { - logger.error({ err, path: req.path, method: req.method }); - const isDev = process.env.NODE_ENV === 'development'; - res.status(500).json({ - error: 'Internal Server Error', - ...(isDev && { message: err.message }), - }); -}); -``` - ---- - -## Headers Anti-Patterns (H1-H8) - -### H1: Missing Content-Security-Policy - -- **Severity**: IMPORTANT -- **Detection**: Absence of `Content-Security-Policy` header -- **OWASP**: A02 - -### H2: CSP with unsafe-inline and unsafe-eval - -- **Severity**: IMPORTANT -- **Detection**: `Content-Security-Policy.*(?:'unsafe-inline'|'unsafe-eval')` -- **OWASP**: A02 - -Use nonce-based CSP: `script-src 'self' 'nonce-{SERVER_GENERATED}'` - -### H3: Missing Strict-Transport-Security - -- **Severity**: IMPORTANT -- **Detection**: Absence of `Strict-Transport-Security` header -- **OWASP**: A02 - -Value: `max-age=31536000; includeSubDomains; preload` - -### H4: Missing X-Content-Type-Options - -- **Severity**: IMPORTANT -- **Detection**: Absence of `X-Content-Type-Options: nosniff` -- **OWASP**: A02 - -### H5: Missing X-Frame-Options - -- **Severity**: IMPORTANT -- **Detection**: Absence of `X-Frame-Options` header -- **OWASP**: A02 - -Value: `DENY`. Also set `Content-Security-Policy: frame-ancestors 'none'`. - -### H6: Permissive Referrer-Policy - -- **Severity**: SUGGESTION -- **Detection**: `Referrer-Policy.*(?:unsafe-url|no-referrer-when-downgrade)` -- **OWASP**: A02 - -Use: `strict-origin-when-cross-origin` - -### H7: Missing Permissions-Policy - -- **Severity**: SUGGESTION -- **Detection**: Absence of `Permissions-Policy` header -- **OWASP**: A02 - -Value: `camera=(), microphone=(), geolocation=(), payment=()` - -### H8: CORS Wildcard with Credentials - -- **Severity**: CRITICAL -- **Detection**: `(?:cors|Access-Control-Allow-Origin).*\*` -- **OWASP**: A02 - -```typescript -// GOOD -app.use(cors({ - origin: ['https://app.example.com', 'https://staging.example.com'], - credentials: true, -})); -``` - ---- - -## Frontend Anti-Patterns (FE1-FE8) - -### FE1: Unsanitized HTML Rendering - -- **Severity**: CRITICAL -- **Detection**: `(?:innerHTML|v-html|dangerouslySetInner)` without DOMPurify -- **OWASP**: A05 - -Always sanitize with DOMPurify before rendering user-controlled HTML. See I4. - -### FE2: Dynamic Code Evaluation with User Input - -- **Severity**: CRITICAL -- **Detection**: `eval\s*\(` -- **OWASP**: A05 - -Use structured data parsers (JSON.parse) instead. - -### FE3: postMessage Without Origin Validation - -- **Severity**: IMPORTANT -- **Detection**: `addEventListener\s*\(\s*['"]message['"].*(?!.*origin)` -- **OWASP**: A01 - -```typescript -window.addEventListener('message', (event) => { - if (event.origin !== 'https://trusted.example.com') return; - processData(event.data); -}); -``` - -### FE4: Prototype Pollution - -- **Severity**: IMPORTANT -- **Detection**: `(?:__proto__|constructor\.prototype|Object\.assign)\s*.*(?:req\.|body\.|query\.)` -- **OWASP**: A05 - -Validate and filter keys from user input before merging into objects. - -### FE5: Open Redirect - -- **Severity**: IMPORTANT -- **Detection**: `(?:window\.location|location\.href|router\.push)\s*=\s*(?:req\.|params\.|query\.)` -- **OWASP**: A01 - -```typescript -// GOOD — relative paths only -const redirect = new URLSearchParams(window.location.search).get('redirect'); -if (redirect?.startsWith('/') && !redirect.startsWith('//')) { - window.location.href = redirect; -} -``` - -### FE6: Sensitive Data in localStorage - -- **Severity**: IMPORTANT -- **Detection**: `localStorage\.setItem\(.*(?:token|session|credit|ssn|password)` -- **OWASP**: A07 - -Use httpOnly cookies for tokens. - -### FE7: Missing CSRF Token - -- **Severity**: IMPORTANT -- **Detection**: POST/PUT/DELETE forms without CSRF token or SameSite cookie -- **OWASP**: A01 - -Use double-submit cookie or synchronizer token. Next.js Server Actions have built-in CSRF via Origin header. - -### FE8: Client-Only Input Validation - -- **Severity**: IMPORTANT -- **Detection**: Form validation only in frontend -- **OWASP**: A05 - -ALWAYS validate on server too. Use zod, joi, or class-validator. - ---- - -## Dependencies Anti-Patterns (D1-D5) - -### D1: Known Vulnerable Dependency - -- **Severity**: CRITICAL -- **Detection**: `npm audit --audit-level=high` exits non-zero -- **OWASP**: A03 - -### D2: Lockfile Out of Sync - -- **Severity**: IMPORTANT -- **Detection**: `npm ci` fails -- **OWASP**: A08 - -### D3: Typosquatting Risk - -- **Severity**: IMPORTANT -- **Detection**: Manual review of new dependency names -- **OWASP**: A03 - -### D4: Postinstall Scripts in New Dependency - -- **Severity**: IMPORTANT -- **Detection**: `"postinstall"` in new dependency's package.json -- **OWASP**: A03 - -### D5: Unpinned Versions in Production - -- **Severity**: SUGGESTION -- **Detection**: `":\s*["']\*["']|":\s*["']latest["']` -- **OWASP**: A03 - ---- - -## API Anti-Patterns (AP1-AP6) - -### AP1: New Endpoint Without Rate Limiting - -- **Severity**: IMPORTANT -- **OWASP**: A05 - -### AP2: GraphQL Without Depth Limiting - -- **Severity**: IMPORTANT -- **Detection**: `new ApolloServer` without depth/complexity limits -- **OWASP**: A05 - -```typescript -import depthLimit from 'graphql-depth-limit'; -const server = new ApolloServer({ - schema, - validationRules: [depthLimit(5)], - introspection: process.env.NODE_ENV !== 'production', -}); -``` - -### AP3: File Upload Without Validation - -- **Severity**: IMPORTANT -- **Detection**: `multer|formidable|busboy` without type/size checks -- **OWASP**: A05 - -```typescript -const upload = multer({ - dest: 'uploads/', - limits: { fileSize: 5 * 1024 * 1024 }, - fileFilter: (req, file, cb) => { - const allowed = ['image/jpeg', 'image/png', 'image/webp']; - cb(null, allowed.includes(file.mimetype)); - }, -}); -``` - -### AP4: Webhook Without Signature Verification - -- **Severity**: CRITICAL -- **OWASP**: A08 - -Always verify webhook signatures (Stripe, GitHub HMAC, etc.). - -### AP5: API Exposing Internal Info - -- **Severity**: IMPORTANT -- **Detection**: `(?:stack|trace|query|sql).*(?:res\.json|res\.send)` -- **OWASP**: A10 - -### AP6: Missing Request Body Size Limit - -- **Severity**: IMPORTANT -- **Detection**: `express\.json\(\)` without `limit` -- **OWASP**: A05 - -```typescript -app.use(express.json({ limit: '100kb' })); -``` - ---- - -## AI/LLM Security Anti-Patterns (AI1-AI3) - -### AI1: Prompt Injection via User Input - -- **Severity**: CRITICAL -- **Detection**: User input concatenated into LLM prompts without sanitization -- **OWASP**: A05 (Injection) - -```typescript -// BAD — user input directly in prompt -const response = await llm.complete(`Summarize this: ${userInput}`); - -// GOOD — structured input with system/user message separation -const response = await llm.complete({ - system: "You are a summarization assistant. Only summarize the provided text.", - user: userInput, -}); -``` - -### AI2: LLM Output Used in SQL/Shell Without Sanitization - -- **Severity**: CRITICAL -- **Detection**: LLM response passed to `db.query()`, `exec()`, or template literals without validation -- **OWASP**: A05 (Injection) - -Never trust LLM output as safe. Treat it as untrusted user input — parameterize queries, escape shell arguments, sanitize HTML before rendering. - -### AI3: Missing Output Validation from LLM Responses - -- **Severity**: IMPORTANT -- **Detection**: LLM response rendered or executed without schema validation -- **OWASP**: A08 (Software or Data Integrity Failures) - -Validate LLM output against expected schemas (Zod, JSON Schema) before using in application logic. Reject responses that don't match expected structure. - ---- - -## Logging Anti-Patterns (L1-L4) - -### L1: Security Events Not Logged - -- **Severity**: IMPORTANT -- **OWASP**: A09 - -Log: auth failures, access denied, rate limit hits, input validation failures, password changes. - -### L2: Sensitive Data in Logs - -- **Severity**: CRITICAL -- **Detection**: `(?:log|logger)\.\w+\(.*(?:password|token|secret|ssn|credit)` -- **OWASP**: A09 - -```typescript -import pino from 'pino'; -const logger = pino({ redact: ['req.headers.authorization', 'req.body.password'] }); -``` - -### L3: Missing Trace IDs - -- **Severity**: SUGGESTION -- **OWASP**: A09 - -### L4: Log Injection - -- **Severity**: IMPORTANT -- **Detection**: `console\.log\(.*\+.*(?:req\.|user\.|body\.)` -- **OWASP**: A09 - -Use structured logging (JSON, auto-escaped) instead of string concatenation. - ---- - -## Framework-Specific: React / Next.js (RX1-RX4) - -### RX1: Server Action Without Auth - -- **Severity**: CRITICAL -- **Detection**: `'use server'` function without `auth()` or session check -- **OWASP**: A01 - -```typescript -'use server'; -import { auth } from '@/auth'; -export async function deleteUser(id: string) { - const session = await auth(); - if (!session?.user || session.user.role !== 'admin') throw new Error('Unauthorized'); - await db.user.delete({ where: { id } }); -} -``` - -### RX2: process.env Without NEXT_PUBLIC_ in Client - -- **Severity**: IMPORTANT -- **Detection**: `'use client'` file accessing `process.env` without `NEXT_PUBLIC_` -- **OWASP**: A02 - -### RX3: RSC Serialization Leaking Data - -- **Severity**: IMPORTANT -- **OWASP**: A01 - -Pick only needed fields before passing DB objects to Client Components. - -### RX4: middleware.ts Not Protecting API Routes - -- **Severity**: IMPORTANT -- **Detection**: `config.matcher` not covering `/api/` -- **OWASP**: A01 - ---- - -## Framework-Specific: Angular (NG1-NG3) - -### NG1: bypassSecurityTrustHtml with User Input - -- **Severity**: CRITICAL -- **Detection**: `bypassSecurityTrust(?:Html|Script|Style|Url|ResourceUrl)` -- **OWASP**: A05 - -Sanitize with DOMPurify BEFORE calling bypassSecurityTrust. - -### NG2: Template Expression Injection - -- **Severity**: IMPORTANT -- **OWASP**: A05 - -Do not use JitCompilerFactory with user-controlled templates. - -### NG3: HttpInterceptor Not Attaching Auth - -- **Severity**: IMPORTANT -- **OWASP**: A07 - -Use a centralized `HttpInterceptorFn` for auth tokens. - ---- - -## Framework-Specific: Express (EX1-EX4) - -### EX1: Missing helmet.js - -- **Severity**: IMPORTANT -- **OWASP**: A02 - -```typescript -import helmet from 'helmet'; -app.use(helmet()); -app.disable('x-powered-by'); -``` - -### EX2: express.json() Without Body Size Limit - -- **Severity**: IMPORTANT -- **OWASP**: A05 - -```typescript -app.use(express.json({ limit: '100kb' })); -``` - -### EX3: Cookie Without Secure Flags - -- **Severity**: IMPORTANT -- **OWASP**: A07 - -```typescript -res.cookie('session', value, { - httpOnly: true, secure: true, sameSite: 'strict', maxAge: 3600000, path: '/', -}); -``` - -### EX4: Error Handler Exposing Stack Trace - -- **Severity**: IMPORTANT -- **OWASP**: A10 - -Only expose error details in development mode. - ---- - -## Framework-Specific: Go (GO1-GO3) - -### GO1: math/rand for Security Operations - -- **Severity**: CRITICAL -- **Detection**: `math/rand` import in security-related files -- **OWASP**: A04 - -Use `crypto/rand` for cryptographically secure random values. - -### GO2: TLS InsecureSkipVerify - -- **Severity**: CRITICAL -- **Detection**: `InsecureSkipVerify:\s*true` -- **OWASP**: A04 - -Use system CA pool (default) instead. - -### GO3: String Interpolation in SQL - -- **Severity**: CRITICAL -- **Detection**: `fmt\.Sprintf\s*\(.*(?:SELECT|INSERT|UPDATE|DELETE|FROM|WHERE)` -- **OWASP**: A05 - -```go -// GOOD — parameterized -db.Where("id = ?", userID).Find(&user) -``` - ---- - -## Security Headers Template - -### helmet.js (Express) - -```typescript -import helmet from 'helmet'; - -app.use(helmet({ - contentSecurityPolicy: { - directives: { - defaultSrc: ["'self'"], - scriptSrc: ["'self'"], - styleSrc: ["'self'"], - imgSrc: ["'self'", "data:", "https:"], - fontSrc: ["'self'"], - connectSrc: ["'self'"], - frameAncestors: ["'none'"], - objectSrc: ["'none'"], - baseUri: ["'self'"], - formAction: ["'self'"], - upgradeInsecureRequests: [], - }, - }, - hsts: { maxAge: 31536000, includeSubDomains: true, preload: true }, - frameguard: { action: 'deny' }, - referrerPolicy: { policy: 'strict-origin-when-cross-origin' }, - crossOriginOpenerPolicy: { policy: 'same-origin' }, - crossOriginResourcePolicy: { policy: 'same-origin' }, -})); -app.disable('x-powered-by'); -``` - ---- - -## JWT Validation Checklist - -1. Verify signature with expected algorithm — reject `alg: none` -2. Enforce algorithm: `algorithms: ['RS256']` or `['ES256']` -3. Check `exp` — reject expired tokens -4. Check `iat` — reject tokens issued too far in the past -5. Check `aud` — reject tokens not intended for this service -6. Check `iss` — reject tokens from unknown issuers -7. Store in httpOnly cookie — not localStorage -8. Use short-lived access tokens (15 min) + refresh token rotation -9. Rotate signing keys periodically - ---- - -## Secure Cookie Flags - -``` -Set-Cookie: session=value; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=3600 -``` - -| Flag | Purpose | When to use | -|------|---------|-------------| -| `HttpOnly` | Not accessible via JavaScript (prevents XSS token theft) | Always | -| `Secure` | Only sent over HTTPS | Always | -| `SameSite=Strict` | Only sent on same-site requests (strongest CSRF) | Auth/session cookies | -| `SameSite=Lax` | Sent on top-level navigations (moderate CSRF) | Cookies that need cross-site top-level nav (e.g., OAuth return) | -| `Path=/` | Limit cookie scope | Always | -| `Max-Age` | Explicit expiration (prefer over `Expires`) | Always | - ---- - -## Security Checklist - -### Authentication and Sessions -- [ ] Passwords hashed with Argon2id or bcrypt (cost >= 12) -- [ ] JWT signed with RS256/ES256, algorithm enforced on verify -- [ ] Access tokens expire in <= 15 minutes -- [ ] Refresh tokens: one-time use, rotated, stored in httpOnly cookie -- [ ] Rate limiting on login, registration, and password reset -- [ ] Session regenerated after authentication -- [ ] MFA available for privileged accounts - -### Authorization -- [ ] Every API endpoint has auth middleware -- [ ] Ownership checks on all resource access (prevent IDOR) -- [ ] Server-side authorization (frontend guards are UX only) -- [ ] Mass assignment prevented (explicit field selection) -- [ ] Re-authentication required for sensitive operations - -### Input and Output -- [ ] All user input validated server-side (zod/joi/class-validator) -- [ ] Parameterized queries for all database operations -- [ ] HTML output sanitized (DOMPurify) when rendering user content -- [ ] Error responses do not expose stack traces in production - -### Secrets -- [ ] No hardcoded secrets in source code -- [ ] `.env` files in `.gitignore` -- [ ] Server secrets not exposed to client (no NEXT_PUBLIC_ on secrets) -- [ ] Environment variables validated at startup - -### Headers -- [ ] Content-Security-Policy configured (nonce-based preferred) -- [ ] Strict-Transport-Security with preload -- [ ] X-Content-Type-Options: nosniff -- [ ] X-Frame-Options: DENY -- [ ] Referrer-Policy: strict-origin-when-cross-origin -- [ ] Permissions-Policy restricting unused APIs -- [ ] CORS restricted to known origins - -### Dependencies -- [ ] `npm audit` (or equivalent) passing in CI -- [ ] Lockfile committed and verified with `npm ci` -- [ ] New dependencies reviewed for typosquatting and postinstall scripts -- [ ] No wildcard or "latest" versions in production - -### Logging -- [ ] Security events logged (auth failures, access denied, rate limits) -- [ ] No sensitive data in logs (passwords, tokens, PII) -- [ ] Structured logging with correlation IDs -- [ ] Alerts configured for anomalous patterns diff --git a/AGENTS.md b/AGENTS.md index bc100f5..66404ee 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -96,6 +96,8 @@ bats tests/test_common.bats | `tests/test_common.bats` | `lib/github-common.sh` — pure-logic functions (`validate_slug`, `require_env_var`, `require_command`, `err`, `configure_gh_auth`, `validate_token`, `get_repo_page_count`, `is_bsd_date`, `csv_escape`) and API helpers (`gh_api` sentinels, `gh_api_paginate`) | | `tests/test_script_validation.bats` | Every script — missing required env vars exit 1, invalid CLI args exit 1, `--help` exits 0, script-specific enum/allowlist validation | | `tests/mock_curl.sh` | Universal drop-in curl mock (used by both test files); response data via env vars `MOCK_CURL_CODE`, `MOCK_CURL_BODY`, `MOCK_CURL_LINK` | +| `tests/test_repo_structure.bats` | Every domain script ships an executable file, `action.yml`, validation tests, and README entries | +| `tests/test_hooks.bats` | `.github/hooks/scripts/block-file-writes-via-bash.sh` denies heredoc file writes | ### What to test for every new script @@ -151,33 +153,10 @@ Use `_mock_curl_200` (defined in `test_script_validation.bats`) when a test must - **CI:** shellcheck + bats unit tests run together on every PR (`.github/workflows/ci.yml`) - **Releases:** automated by Release Please (`.github/workflows/release.yml`) — pushes to `main` trigger a release PR; merging it publishes the GitHub Release and tag -## Commit Messages — Conventional Commits (required) +## Commit Messages -All commits **must** follow [Conventional Commits](https://www.conventionalcommits.org/). `CHANGELOG.md` is fully managed by Release Please and **must never be edited manually**. - -| Prefix | Version bump | Visible in changelog | -|--------|-------------|----------------------| -| `feat:` | minor | ✅ Features | -| `fix:` | patch | ✅ Bug Fixes | -| `docs:` | patch | ✅ Documentation | -| `perf:` | patch | ✅ Performance Improvements | -| `revert:` | patch | ✅ Reverts | -| `feat!:` / `BREAKING CHANGE:` | major | ✅ | -| `chore:` | none | hidden | -| `ci:` | none | hidden | -| `refactor:` | patch | Code Refactoring | -| `test:` | none | hidden | - ---- +Use [Conventional Commits](https://www.conventionalcommits.org/); Release Please generates `CHANGELOG.md`, so leave that file untouched. Prefix-to-bump table: "Commit Messages" in [`.github/copilot-instructions.md`](.github/copilot-instructions.md). ## Common Pitfalls -- **Team slugs vs names:** API uses slugs (lowercase, hyphenated). "Platform Team" → `platform-team` -- **Bearer vs token auth:** Enterprise endpoints need `Bearer`; standard org/repo endpoints use `token` -- **Space-separated lists:** `REPO_ADMIN` and similar accept multiple space-separated values — loop with `for item in ${VAR}` -- **Comma-separated lists:** `COLLABORATORS`, `REPO_NAMES`, `ORGS` use commas — split with `IFS=','` -- **URL encoding:** Labels in API calls must be URL-encoded (e.g., `Linked [AC]` → `Linked%20[AC]`) -- **Public repo filtering:** Do not rely on `?type=public` for enterprise-managed orgs — fetch all and filter in `jq` -- **macOS vs Linux date:** `github-archive-old-repos.sh` handles both BSD `date -v` and GNU `date -d` -- **`set -euo pipefail`:** Must be the first executable line after the header — never omit it -- **Never edit `CHANGELOG.md` manually** — it is fully managed by Release Please via Conventional Commits +Slug, auth-header, list-separator, URL-encoding, and date pitfalls: "Common Pitfalls" in [`.github/copilot-instructions.md`](.github/copilot-instructions.md). Per-script quirks: [`docs/script-behaviors.md`](docs/script-behaviors.md). diff --git a/docs/script-behaviors.md b/docs/script-behaviors.md new file mode 100644 index 0000000..c8781dc --- /dev/null +++ b/docs/script-behaviors.md @@ -0,0 +1,77 @@ +# Script-Specific Behaviors + +### `github-add-enterprise-team-read-permissions` + +Uses GraphQL cursor-based pagination to enumerate all enterprise orgs, then assigns the `all_repo_read` org role (configurable via `ALL_REPO_READ_ROLE_NAME`) to the enterprise team in each org. Requires Bearer token authentication with `admin:enterprise` scope. The `ENTERPRISE_TEAM_SLUG` is the slug without the `"ent:"` prefix. + +### `github-add-repo-collaborators-by-pattern` + +Adds individual collaborators to org repos whose names match `REPO_NAME_REGEX` (ERE). Accepts a comma-separated `COLLABORATORS` list and an optional `REPO_EXCLUDE_REGEX` to skip matching repos. Uses `token` auth for all REST calls. Iterates all org repos paginated at 100 per page. + +### `github-add-repo-permissions` + +Grants team permissions across all repos in an org. Reads five space-separated team-slug lists (`REPO_ADMIN`, `REPO_MAINTAIN`, `REPO_PUSH`, `REPO_TRIAGE`, `REPO_PULL`). Optionally filters repos by name prefix via `REPO_NAME_FILTER`. Sleeps 5 s between each repo to stay within rate limits. Uses legacy `Accept: application/vnd.github.v3+json` and `token` auth. + +### `github-archive-old-repos` + +Calculates a cutoff date based on `YEARS_THRESHOLD` (default 5). Generates a timestamped CSV report under `reports/`, shows the top 10 oldest repos, and prompts for confirmation before archiving. Handles both BSD (`date -v`, macOS) and GNU (`date -d`, Linux) date syntax for cross-platform compatibility. + +### `github-auto-repo-creation` + +Creates private repos from a comma-separated `REPO_NAMES` list. Configures branch protection on the default branch, creates a CODEOWNERS file (Base64-encoded via API), and grants admin access to comma-separated `ADMIN_TEAMS`. All slug values in `REPO_NAMES`, `ADMIN_TEAMS`, and `REPO_OWNERS` are validated before use. Requires `base64` in addition to `curl` and `jq`. + +### `github-close-archived-repo-security-alerts` + +Dismisses open Dependabot, code-scanning, and secret-scanning alerts on all archived repos. Supports `--type` to target a single alert type and `--dry-run` to preview without changes. Generates a timestamped CSV report under `reports/`. Requires a token with `security_events` and `repo` scopes. + +### `github-dockerfile-discovery` + +Uses GitHub code-search API to find Dockerfiles across all enterprise orgs, then fetches and parses each file to extract `FROM` instructions (including multi-stage builds). Produces three timestamped reports in `REPORT_DIR`: a detail CSV, a summary CSV, and a plain-text summary. Supports `ORGS` override, `ORG_FILTER`/`ORG_EXCLUDE` regex filters (validated as syntactically correct ERE before use), and configurable sleep intervals (`SEARCH_SLEEP`, `CONTENT_SLEEP`). Both `gh_api` call sites handle sentinels: `__422__` or `__404__` on the code-search endpoint skips that org with a warning; `__404__` or `__422__` on the file-contents endpoint skips that individual file. + +### `github-enable-issues` + +Iterates all non-archived repos in an org and enables the Issues feature on any repo where it is disabled. Supports `--dry-run` to list affected repos without making changes. + +### `github-get-consumed-licenses` + +Calls the enterprise consumed-licenses endpoint using Bearer authentication. Requires `read:enterprise` scope. Returns seat consumption and purchase counts. Token-only script; no pagination or retry logic beyond what the single API call provides. + +### `github-get-public-repos` + +Discovers all enterprise orgs (three-tier fallback via `get_enterprise_orgs`), fetches all repos in each, and filters to public visibility in `jq` (does not rely on `?type=public` query param, which is unreliable for enterprise-managed orgs). Writes a timestamped CSV to `REPORT_DIR`. Supports `ORGS` override and `ORG_FILTER`/`ORG_EXCLUDE` ERE regex filters. + +### `github-get-repo-list` + +Outputs a CSV row per repository (full name, owner, visibility, URL, description, fork flag, pushed/created/updated timestamps) to stdout. Pagination handled at 100 repos per page via `get_repo_page_count`. + +### `github-import-repo` + +Performs a full bare clone (`git clone --mirror`) of a source repo and pushes all branches, tags, and history to a new private destination repo. Validates `GIT_URL_PREFIX` against a GitHub-host allowlist before running any git operations to prevent credential leakage. Repo names and `OWNER_USERNAME` are validated as slugs. + +### `github-migrate-internal-repos-to-private` + +Fetches all repos with internal visibility (paginated) and converts each to private via PATCH. Logs success or failure per repo. This operation cannot be undone via the API — converting internal to private removes access from members of other enterprise orgs. + +### `github-monthly-issues-report` + +Generates an HTML report of issues created in a date range (`MONTH_START`/`MONTH_END`), filtered by a hardcoded label (`Linked [AC]` — edit the script to change). Uses the timeline API to track who applied labels. URL-encodes labels for API calls (e.g., `Linked [AC]` → `Linked%20[AC]`). Outputs to `output.txt`. + +### `github-organize-stars` + +Uses `gh` CLI GraphQL (not `curl`) to fetch all starred repos. Accepts `GITHUB_TOKEN` env var or an active `gh` auth session for authentication. Categorizes by primary language, GitHub topics, and name keywords using a `RULES` array (pipe-delimited: `List Name|LANGUAGES|TOPICS|NAME_KEYWORDS`; first matching rule wins). Caches stars at `~/.cache/gh-star-organizer/stars.json`. Supports `--dry-run`, `-y` (skip confirm), `--show-repos`, and `--no-cache`. Adds repos to Lists in batches of 25. + +### `github-repo-from-template` + +Creates a private repo from `TEMPLATE_REPO` (including all branches), assigns admin permissions to space-separated `REPO_ADMIN` teams, write permissions to `REPO_WRITE` teams, then invites `CD_USERNAME` as a collaborator using `CD_GITHUB_TOKEN` and auto-accepts the invitation. All slug values are validated before use. + +### `github-repo-permissions-report` + +Uses `curl` for all API calls. Accepts `GITHUB_TOKEN` env var or an active `gh` auth session (token auto-resolved at lib source time) for authentication. Accepts `-r OWNER/REPO`, `-b BRANCH`, and `-o FILE` flags. Outputs a CSV with two record types: `permission` (all users/teams) and `bypass_actor` (explicit PR approval bypass entries). Reports both branch protection rules and repository rulesets. + +### `github-copilot-report` + +Uses `curl` for all GitHub API calls. Accepts `GITHUB_TOKEN` env var (PAT with `read:enterprise` and `manage_billing:enterprise` scopes) or an active `gh` auth session (token auto-resolved at lib source time) for authentication. Also requires `az` to be **installed** when Entra ID enrichment is needed — `az` is checked at runtime only if `--no-entra` is not set and `az` is not already skipped. If `az` is not installed or not logged in, Entra lookup is silently skipped. When `az` is logged in, enriches each user with Entra ID department and job title via `az rest`. + +Auto-detects credits per seat from a promo/standard table keyed on plan type and today's date (promo period Jun 1 – Sep 1, 2026); override with `--credits N` or `$CREDITS_PER_SEAT_OVERRIDE`. Credits are pooled enterprise-wide, not per-user buckets. Code completions are not billed in AI credits. + +Uses API version `2026-03-10` and the new usage-metrics NDJSON endpoints (signed download links). The legacy `/copilot/metrics` and `/copilot/usage` endpoints were closed Apr 2, 2026. diff --git a/tests/test_common.bats b/tests/test_common.bats index 637469a..62d2f44 100644 --- a/tests/test_common.bats +++ b/tests/test_common.bats @@ -18,7 +18,11 @@ LIB_PATH="${BATS_TEST_DIRNAME}/../lib/github-common.sh" MOCK_BIN="" setup() { + export GH_API_RATE_LIMIT_SLEEP=0 GH_API_ERROR_SLEEP=0 MOCK_BIN="$(mktemp -d)" + # sleep mock: retry delays in gh_api must not slow the suite + printf '#!/bin/sh\nexit 0\n' > "$MOCK_BIN/sleep" + chmod +x "$MOCK_BIN/sleep" # Default gh mock: fails all calls so GITHUB_TOKEN is not auto-resolved printf '#!/bin/sh\nexit 1\n' > "$MOCK_BIN/gh" chmod +x "$MOCK_BIN/gh" diff --git a/tests/test_hooks.bats b/tests/test_hooks.bats new file mode 100644 index 0000000..8f391f0 --- /dev/null +++ b/tests/test_hooks.bats @@ -0,0 +1,59 @@ +#!/usr/bin/env bats +# ============================================================================= +# tests/test_hooks.bats +# +# Verifies .github/hooks/scripts/block-file-writes-via-bash.sh denies heredoc +# and tee file writes and allows ordinary commands. +# +# Usage: +# bats tests/test_hooks.bats +# ============================================================================= + +HOOK="${BATS_TEST_DIRNAME}/../.github/hooks/scripts/block-file-writes-via-bash.sh" + +# $1 = tool name, $2 = shell command the agent wants to run +_run_hook() { + local payload + payload=$(jq -nc --arg t "$1" --arg c "$2" '{toolName: $t, toolArgs: ({command: $c} | tojson)}') + run bash "${HOOK}" <<<"${payload}" +} + +@test "hook: denies cat > file with a quoted heredoc" { + _run_hook bash "cat > notes.md <<'EOF' +hello +EOF" + [ "$status" -eq 0 ] + [[ "$output" == *'"permissionDecision":"deny"'* ]] +} + +@test "hook: denies cat >> file with an unquoted heredoc" { + _run_hook bash "cat >> tests/a.bats << EOF +x +EOF" + [[ "$output" == *'"permissionDecision":"deny"'* ]] +} + +@test "hook: denies tee file with a heredoc" { + _run_hook bash "tee out.txt <<-EOF +x +EOF" + [[ "$output" == *'"permissionDecision":"deny"'* ]] +} + +@test "hook: allows ordinary commands" { + _run_hook bash "bats tests/ && git status --short" + [ "$status" -eq 0 ] + [ -z "$output" ] +} + +@test "hook: allows a heredoc that does not write a file" { + _run_hook bash "python3 - <<'EOF' +print(1) +EOF" + [ -z "$output" ] +} + +@test "hook: ignores other tools" { + _run_hook edit "cat > f </github-/github-.sh: +# - the script is executable +# - action.yml sits beside it +# - tests/test_script_validation.bats has tests for it +# - README.md documents it (usage section and Available Actions table row) +# +# Usage: +# bats tests/test_repo_structure.bats +# ============================================================================= + +REPO_ROOT="${BATS_TEST_DIRNAME}/.." + +# Prints the paths of every domain script, one per line. +_scripts() { + find "${REPO_ROOT}"/{org-admin,enterprise,reporting,personal} \ + -mindepth 2 -maxdepth 2 -name 'github-*.sh' 2>/dev/null | sort +} + +# Runs a check function on every script and fails with the list of scripts +# that do not satisfy it. +_assert_all_scripts() { + local check="$1" script failures="" + while IFS= read -r script; do + "${check}" "${script}" || failures+=" ${script#"${REPO_ROOT}"/}"$'\n' + done < <(_scripts) + if [ -n "${failures}" ]; then + echo "Scripts failing ${check#_check_}:" + echo "${failures}" + return 1 + fi +} + +# Local interactive tools that are intentionally not exposed as GitHub Actions. +_no_action() { [[ "$(basename "$1")" == "github-organize-stars.sh" ]]; } + +_check_executable() { [ -x "$1" ]; } +_check_action_yml() { _no_action "$1" || [ -f "$(dirname "$1")/action.yml" ]; } +_check_bats_tests() { grep -q "^@test \"$(basename "$1" .sh):" "${REPO_ROOT}/tests/test_script_validation.bats"; } +_check_readme_script() { grep -q "$(basename "$(dirname "$1")")/$(basename "$1")" "${REPO_ROOT}/README.md"; } +_check_readme_actions_row() { _no_action "$1" || grep -q "^| \`[a-z-]*/$(basename "$1" .sh)\` |" "${REPO_ROOT}/README.md"; } + +@test "structure: at least one domain script is discovered" { + [ -n "$(_scripts)" ] +} + +@test "structure: every script is executable" { + _assert_all_scripts _check_executable +} + +@test "structure: every script has an action.yml" { + _assert_all_scripts _check_action_yml +} + +@test "structure: every script has tests in test_script_validation.bats" { + _assert_all_scripts _check_bats_tests +} + +@test "structure: every script is documented in README.md" { + _assert_all_scripts _check_readme_script +} + +@test "structure: every script has a row in the README Available Actions table" { + _assert_all_scripts _check_readme_actions_row +} diff --git a/tests/test_script_validation.bats b/tests/test_script_validation.bats index 6d4940b..f2cb66c 100644 --- a/tests/test_script_validation.bats +++ b/tests/test_script_validation.bats @@ -25,6 +25,9 @@ MOCK_BIN="" setup() { MOCK_BIN="$(mktemp -d)" + # sleep mock: rate-limit delays in scripts and lib must not slow the suite + printf '#!/bin/sh\nexit 0\n' > "$MOCK_BIN/sleep" + chmod +x "$MOCK_BIN/sleep" # gh mock: fail all calls so GITHUB_TOKEN is never auto-resolved from a session printf '#!/bin/sh\nexit 1\n' > "$MOCK_BIN/gh" chmod +x "$MOCK_BIN/gh"