From 11a3362128b11fe091eac49e1777e8e55d24fc4d Mon Sep 17 00:00:00 2001 From: lelia <2418071+lelia@users.noreply.github.com> Date: Wed, 12 Aug 2026 15:50:46 -0700 Subject: [PATCH] fix(release): keep documentation versions in sync --- .github/PULL_REQUEST_TEMPLATE.md | 15 ++- .github/workflows/python-tests.yml | 12 +++ README.md | 6 +- docs/github-action.md | 56 +++++------ docs/local-install-docker.md | 82 ++++++++-------- docs/pre-commit-hook.md | 2 +- scripts/check_release_docs.py | 150 +++++++++++++++++++++++++++++ scripts/prep_release.py | 29 +++++- tests/test_release_docs.py | 63 ++++++++++++ 9 files changed, 329 insertions(+), 86 deletions(-) create mode 100644 scripts/check_release_docs.py create mode 100644 tests/test_release_docs.py diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 7b65fcf..4952a08 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -19,11 +19,10 @@ ### Release checklist (skip for non-release PRs) - - -- [ ] `pyproject.toml` `version:` field updated to new version -- [ ] `python3 scripts/sync_release_version.py --write` run after updating `pyproject.toml` -- [ ] `socket_basics/version.py` updated to new version -- [ ] `socket_basics/__init__.py` updated to the same version -- [ ] `action.yml` `image:` ref updated to `docker://ghcr.io/socketdev/socket-basics:` -- [ ] `CHANGELOG.md` updated with human-authored release notes for this version + + +- [ ] `python3 scripts/prep_release.py --version ` completed successfully +- [ ] Release metadata and `uv.lock` are synchronized +- [ ] Current-release references in README and docs are synchronized +- [ ] `CHANGELOG.md` contains reviewed, human-authored notes for this version +- [ ] Release PR will be merged before its merge commit is tagged diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index 380a7ac..251aed1 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -9,6 +9,11 @@ on: - "pyproject.toml" - "uv.lock" - "action.yml" + - "README.md" + - "docs/**/*.md" + - "scripts/check_release_docs.py" + - "scripts/prep_release.py" + - "scripts/sync_release_version.py" - ".github/workflows/python-tests.yml" pull_request: paths: @@ -17,6 +22,11 @@ on: - "pyproject.toml" - "uv.lock" - "action.yml" + - "README.md" + - "docs/**/*.md" + - "scripts/check_release_docs.py" + - "scripts/prep_release.py" + - "scripts/sync_release_version.py" - ".github/workflows/python-tests.yml" workflow_dispatch: @@ -53,5 +63,7 @@ jobs: run: uv lock --locked - name: ๐Ÿ”’ Assert release version metadata is in sync run: python3 scripts/sync_release_version.py --check + - name: ๐Ÿ“š Assert current-release docs are in sync + run: python3 scripts/check_release_docs.py --check - name: ๐Ÿงช Run tests run: uv run --no-sync pytest -q tests/ diff --git a/README.md b/README.md index 426dc74..f9cf878 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ jobs: - name: Run Socket Basics # Pin to a commit SHA for supply-chain safety. # Dependabot will keep this up to date automatically โ€” see docs/github-action.md. - uses: SocketDev/socket-basics@ # v2.0.3 + uses: SocketDev/socket-basics@ # v3.0.0 env: GITHUB_PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }} with: @@ -178,10 +178,10 @@ For GitHub Actions, see the [Quick Start](#-quick-start---github-actions) above ```bash # Pull the pre-built image (recommended โ€” no build step required) -docker pull ghcr.io/socketdev/socket-basics:2.0.3 +docker pull ghcr.io/socketdev/socket-basics:3.0.0 # Run scan -docker run --rm -v "$PWD:/workspace" ghcr.io/socketdev/socket-basics:2.0.3 \ +docker run --rm -v "$PWD:/workspace" ghcr.io/socketdev/socket-basics:3.0.0 \ --workspace /workspace \ --python-sast-enabled \ --secret-scanning-enabled \ diff --git a/docs/github-action.md b/docs/github-action.md index 93cc83f..2821f3f 100644 --- a/docs/github-action.md +++ b/docs/github-action.md @@ -43,7 +43,7 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Run Socket Basics - uses: SocketDev/socket-basics@v2.0.3 + uses: SocketDev/socket-basics@v3.0.0 env: GITHUB_PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }} with: @@ -57,7 +57,7 @@ With just your `SOCKET_SECURITY_API_KEY`, all scanning configurations are manage ### How the action is currently built -When you reference `uses: SocketDev/socket-basics@v2.0.3`, GitHub Actions pulls the +When you reference `uses: SocketDev/socket-basics@v3.0.0`, GitHub Actions pulls the pre-built image referenced by [`action.yml`](../action.yml). The historical multi-stage Docker build still matters for maintainers because it determines what lands in the published image: @@ -75,7 +75,7 @@ Socket Basics from source in every workflow run. ### Pre-built image Starting with v2, the action pulls a pre-built image from GHCR rather than -building from source on every run. Pinning to a specific version tag (e.g. `@v2.0.3`) +building from source on every run. Pinning to a specific version tag (e.g. `@v3.0.0`) means the action starts in seconds โ€” the image is built, integration-tested, and published before the release tag is ever created. @@ -85,7 +85,7 @@ If you run socket-basics in other CI systems (Jenkins, GitLab, CircleCI, etc.) o as a standalone `docker run`, pull the pre-built image directly: ```bash -docker pull ghcr.io/socketdev/socket-basics:2.0.3 +docker pull ghcr.io/socketdev/socket-basics:3.0.0 ``` See [Local Docker Installation](local-install-docker.md) for usage examples. @@ -101,7 +101,7 @@ is immediately affected. We've seen this happen across the ecosystem: publish `:latest`/`:latest-heavy` Docker aliases as an onboarding convenience, but treat them as exactly that โ€” production pipelines should pin an exact version or digest.) -- **Version tags** (`@v2.0.3`) are better, but tags are mutable by default. +- **Version tags** (`@v3.0.0`) are better, but tags are mutable by default. A tag can be deleted and recreated pointing at a different commit. There are documented cases of this happening โ€” maliciously and accidentally. - **Commit SHAs** are the only truly immutable reference. A SHA cannot be @@ -126,14 +126,14 @@ The only truly immutable reference. Dependabot keeps it current automatically. ```yaml - name: Run Socket Basics # Dependabot keeps this SHA up to date โ€” see .github/dependabot.yml setup below. - uses: SocketDev/socket-basics@ # v2.0.3 + uses: SocketDev/socket-basics@ # v3.0.0 with: socket_security_api_key: ${{ secrets.SOCKET_SECURITY_API_KEY }} ``` Get the SHA for any release: ```bash -git ls-remote https://github.com/SocketDev/socket-basics refs/tags/v2.0.3 +git ls-remote https://github.com/SocketDev/socket-basics refs/tags/v3.0.0 ``` --- @@ -145,7 +145,7 @@ enforces tag protection rules). SHA pinning is still preferable for defence in depth. ```yaml -- uses: SocketDev/socket-basics@v2.0.3 +- uses: SocketDev/socket-basics@v3.0.0 with: socket_security_api_key: ${{ secrets.SOCKET_SECURITY_API_KEY }} ``` @@ -166,7 +166,7 @@ updates: ``` Dependabot opens a PR for each new release, updating the SHA or version tag -and keeping the `# v2.0.3` comment in sync. You review, approve, and merge +and keeping the `# v3.0.0` comment in sync. You review, approve, and merge on your own schedule โ€” automated upgrades with a human gate. --- @@ -176,7 +176,7 @@ on your own schedule โ€” automated upgrades with a human gate. | Strategy | Immutable? | Auto-updates | Review gate | |---|---|---|---| | `@v2` floating tag | โŒ (not published) | โ€” | โ€” | -| `@v2.0.3` + Dependabot | โœ… (tag protection enforced) | Yes (weekly PR) | Yes | +| `@v3.0.0` + Dependabot | โœ… (tag protection enforced) | Yes (weekly PR) | Yes | | `@` + Dependabot | โœ… always | Yes (weekly PR) | Yes | ## Basic Configuration @@ -204,7 +204,7 @@ Include these in your workflow's `jobs..permissions` section. **SAST (Static Analysis):** ```yaml -- uses: SocketDev/socket-basics@v2.0.3 +- uses: SocketDev/socket-basics@v3.0.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} # Enable SAST for specific languages @@ -218,7 +218,7 @@ Include these in your workflow's `jobs..permissions` section. **Secret Scanning:** ```yaml -- uses: SocketDev/socket-basics@v2.0.3 +- uses: SocketDev/socket-basics@v3.0.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} secret_scanning_enabled: 'true' @@ -230,7 +230,7 @@ Include these in your workflow's `jobs..permissions` section. **Container Scanning:** ```yaml -- uses: SocketDev/socket-basics@v2.0.3 +- uses: SocketDev/socket-basics@v3.0.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} # The supported pre-built GitHub Action path currently ships without @@ -252,7 +252,7 @@ Include these in your workflow's `jobs..permissions` section. **Socket Tier 1 Reachability:** ```yaml -- uses: SocketDev/socket-basics@v2.0.3 +- uses: SocketDev/socket-basics@v3.0.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} socket_tier_1_enabled: 'true' @@ -261,7 +261,7 @@ Include these in your workflow's `jobs..permissions` section. ### Output Configuration ```yaml -- uses: SocketDev/socket-basics@v2.0.3 +- uses: SocketDev/socket-basics@v3.0.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} python_sast_enabled: 'true' @@ -299,7 +299,7 @@ jobs: fetch-depth: 0 - name: Run Socket Basics (changed files only) - uses: SocketDev/socket-basics@v2.0.3 + uses: SocketDev/socket-basics@v3.0.0 env: GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }} with: @@ -348,7 +348,7 @@ Configure Socket Basics centrally from the [Socket Dashboard](https://socket.dev **Enable in workflow:** ```yaml -- uses: SocketDev/socket-basics@v2.0.3 +- uses: SocketDev/socket-basics@v3.0.0 env: GITHUB_PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }} with: @@ -361,7 +361,7 @@ Configure Socket Basics centrally from the [Socket Dashboard](https://socket.dev > [!NOTE] > You can also pass credentials using environment variables instead of the `with:` section: > ```yaml -> - uses: SocketDev/socket-basics@v2.0.3 +> - uses: SocketDev/socket-basics@v3.0.0 > env: > SOCKET_SECURITY_API_KEY: ${{ secrets.SOCKET_SECURITY_API_KEY }} > with: @@ -379,7 +379,7 @@ All notification integrations require Socket Enterprise. **Slack Notifications:** ```yaml -- uses: SocketDev/socket-basics@v2.0.3 +- uses: SocketDev/socket-basics@v3.0.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} socket_org: ${{ secrets.SOCKET_ORG }} @@ -391,7 +391,7 @@ All notification integrations require Socket Enterprise. **Jira Issue Creation:** ```yaml -- uses: SocketDev/socket-basics@v2.0.3 +- uses: SocketDev/socket-basics@v3.0.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} socket_org: ${{ secrets.SOCKET_ORG }} @@ -406,7 +406,7 @@ All notification integrations require Socket Enterprise. **Microsoft Teams:** ```yaml -- uses: SocketDev/socket-basics@v2.0.3 +- uses: SocketDev/socket-basics@v3.0.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} socket_org: ${{ secrets.SOCKET_ORG }} @@ -418,7 +418,7 @@ All notification integrations require Socket Enterprise. **Generic Webhook:** ```yaml -- uses: SocketDev/socket-basics@v2.0.3 +- uses: SocketDev/socket-basics@v3.0.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} socket_org: ${{ secrets.SOCKET_ORG }} @@ -430,7 +430,7 @@ All notification integrations require Socket Enterprise. **SIEM Integration:** ```yaml -- uses: SocketDev/socket-basics@v2.0.3 +- uses: SocketDev/socket-basics@v3.0.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} socket_org: ${{ secrets.SOCKET_ORG }} @@ -466,7 +466,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Run Socket Basics - uses: SocketDev/socket-basics@v2.0.3 + uses: SocketDev/socket-basics@v3.0.0 env: GITHUB_PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }} with: @@ -509,7 +509,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Run Full Security Scan - uses: SocketDev/socket-basics@v2.0.3 + uses: SocketDev/socket-basics@v3.0.0 env: GITHUB_PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }} with: @@ -634,7 +634,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Run Socket Basics - uses: SocketDev/socket-basics@v2.0.3 + uses: SocketDev/socket-basics@v3.0.0 env: GITHUB_PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }} with: @@ -691,7 +691,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Run Socket Basics - uses: SocketDev/socket-basics@v2.0.3 + uses: SocketDev/socket-basics@v3.0.0 env: GITHUB_PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }} with: @@ -820,7 +820,7 @@ env: ```yaml steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - Must be first - - uses: SocketDev/socket-basics@v2.0.3 + - uses: SocketDev/socket-basics@v3.0.0 ``` ### PR Comments Not Appearing diff --git a/docs/local-install-docker.md b/docs/local-install-docker.md index e149dd1..05fd132 100644 --- a/docs/local-install-docker.md +++ b/docs/local-install-docker.md @@ -16,7 +16,7 @@ Run Socket Basics locally using Docker without installing security tools on your ```bash # 1. Pull a pinned release from GHCR (no build step required) -docker pull ghcr.io/socketdev/socket-basics:2.0.3 +docker pull ghcr.io/socketdev/socket-basics:3.0.0 # 2. Create .env file with your credentials cat > .env << 'EOF' @@ -28,14 +28,14 @@ EOF docker run --rm \ -v "$PWD:/workspace" \ --env-file .env \ - ghcr.io/socketdev/socket-basics:2.0.3 \ + ghcr.io/socketdev/socket-basics:3.0.0 \ --workspace /workspace \ --python \ --secrets \ --console-tabular-enabled ``` -The Docker image should always be pinned to an exact version such as `2.0.3`. Avoid +The Docker image should always be pinned to an exact version such as `3.0.0`. Avoid floating tags like `:latest` in CI/CD. ## Using Pre-built Images @@ -45,12 +45,12 @@ The baked-in security tool versions are recorded in the image labels so you can inspect exactly what's inside: ```bash -docker inspect ghcr.io/socketdev/socket-basics:2.0.3 \ +docker inspect ghcr.io/socketdev/socket-basics:3.0.0 \ | jq '.[0].Config.Labels' # { # "com.socket.trufflehog-version": "3.93.8", # "com.socket.opengrep-version": "v1.16.5", -# "org.opencontainers.image.version": "2.0.3", +# "org.opencontainers.image.version": "3.0.0", # ... # } ``` @@ -81,7 +81,7 @@ docker inspect ghcr.io/socketdev/socket-basics:2.0.3 \ -v "$GITHUB_WORKSPACE:/workspace" \ -e SOCKET_SECURITY_API_KEY=${{ secrets.SOCKET_API_KEY }} \ -e SOCKET_ORG=${{ secrets.SOCKET_ORG }} \ - ghcr.io/socketdev/socket-basics:2.0.3 \ + ghcr.io/socketdev/socket-basics:3.0.0 \ --workspace /workspace \ --all-languages \ --secrets \ @@ -92,7 +92,7 @@ docker inspect ghcr.io/socketdev/socket-basics:2.0.3 \ ```yaml security-scan: - image: ghcr.io/socketdev/socket-basics:2.0.3 + image: ghcr.io/socketdev/socket-basics:3.0.0 stage: test script: - socket-basics @@ -109,7 +109,7 @@ security-scan: ```dockerfile # Pin socket-basics and let Dependabot send upgrade PRs automatically -FROM ghcr.io/socketdev/socket-basics:2.0.3 +FROM ghcr.io/socketdev/socket-basics:3.0.0 ``` ### Staying Up to Date with Dependabot @@ -127,7 +127,7 @@ updates: interval: "weekly" ``` -Dependabot will detect the `FROM ghcr.io/socketdev/socket-basics:2.0.3` reference +Dependabot will detect the `FROM ghcr.io/socketdev/socket-basics:3.0.0` reference and open a PR with the version bump when a new release is available. ## Building the Docker Image @@ -138,10 +138,10 @@ Pull a specific release without building locally: ```bash # GHCR (preferred) -docker pull ghcr.io/socketdev/socket-basics:2.0.3 +docker pull ghcr.io/socketdev/socket-basics:3.0.0 # Docker Hub -docker pull socketdev/socket-basics:2.0.3 +docker pull socketdev/socket-basics:3.0.0 ``` ### Build from Source @@ -154,7 +154,7 @@ git clone https://github.com/SocketDev/socket-basics.git cd socket-basics # Build with version tag (multi-stage; first build is slower, subsequent ones are fast) -docker build -t socket-basics:2.0.3 . +docker build -t socket-basics:3.0.0 . # Verify the build docker images | grep socket-basics @@ -163,7 +163,7 @@ docker images | grep socket-basics ### Build for a Specific Platform (M1/M2 Macs) ```bash -docker build --platform linux/amd64 -t socket-basics:2.0.3 . +docker build --platform linux/amd64 -t socket-basics:3.0.0 . ``` ### Build with Custom Tool Versions @@ -174,7 +174,7 @@ The image pins the bundled tools to specific versions. You can override them at docker build \ --build-arg TRUFFLEHOG_VERSION=3.93.8 \ --build-arg OPENGREP_VERSION=v1.16.5 \ - -t socket-basics:2.0.3 . + -t socket-basics:3.0.0 . ``` Trivy comes from a Socket-built image pinned by digest via the `TRIVY_IMAGE` @@ -187,10 +187,10 @@ tests image, build from the `app_tests` directory and use the same build args. ```bash # Check that all tools are available in the container -docker run --rm socket-basics:2.0.3 socket-basics --version -docker run --rm socket-basics:2.0.3 socket --version -docker run --rm socket-basics:2.0.3 opengrep --version -docker run --rm socket-basics:2.0.3 trufflehog --version +docker run --rm socket-basics:3.0.0 socket-basics --version +docker run --rm socket-basics:3.0.0 socket --version +docker run --rm socket-basics:3.0.0 opengrep --version +docker run --rm socket-basics:3.0.0 trufflehog --version ``` ### Smoke Test @@ -225,7 +225,7 @@ Mount your project directory into the container: # Scan current directory docker run --rm \ -v "$PWD:/workspace" \ - socket-basics:2.0.3 \ + socket-basics:3.0.0 \ --workspace /workspace \ --python \ --secrets \ @@ -242,7 +242,7 @@ docker run --rm \ # Scan a specific project directory docker run --rm \ -v "/path/to/your/project:/workspace" \ - socket-basics:2.0.3 \ + socket-basics:3.0.0 \ --workspace /workspace \ --javascript \ --secrets @@ -253,7 +253,7 @@ docker run --rm \ ```bash docker run --rm \ -v "$PWD:/workspace" \ - socket-basics:2.0.3 \ + socket-basics:3.0.0 \ --workspace /workspace \ --all-languages \ --secrets \ @@ -301,7 +301,7 @@ VERBOSE=false docker run --rm \ -v "$PWD:/workspace" \ --env-file .env \ - socket-basics:2.0.3 \ + socket-basics:3.0.0 \ --workspace /workspace \ --python \ --secrets @@ -316,7 +316,7 @@ docker run --rm \ -v "$PWD:/workspace" \ -e "SOCKET_SECURITY_API_KEY=scrt_your_api_key" \ -e "SOCKET_ORG=your-org-slug" \ - socket-basics:2.0.3 \ + socket-basics:3.0.0 \ --workspace /workspace \ --python \ --secrets \ @@ -338,7 +338,7 @@ docker run --rm \ --env-file .env.socket \ --env-file .env.notifiers \ --env-file .env.scanning \ - socket-basics:2.0.3 \ + socket-basics:3.0.0 \ --workspace /workspace \ --all-languages ``` @@ -357,7 +357,7 @@ docker run --rm \ -v "$PWD:/workspace" \ -e "SOCKET_SECURITY_API_KEY=$SOCKET_SECURITY_API_KEY" \ -e "SOCKET_ORG=$SOCKET_ORG" \ - socket-basics:2.0.3 \ + socket-basics:3.0.0 \ --workspace /workspace \ --python ``` @@ -385,7 +385,7 @@ docker run --rm \ -v "$PWD:/workspace" \ -v "$PWD/scan-results:/results" \ --env-file .env \ - socket-basics:2.0.3 \ + socket-basics:3.0.0 \ --workspace /workspace \ --python \ --secrets \ @@ -402,7 +402,7 @@ docker run --rm -it \ -v "$PWD:/workspace" \ --env-file .env \ --entrypoint /bin/bash \ - socket-basics:2.0.3 + socket-basics:3.0.0 # Inside container, run commands manually: # cd /workspace @@ -431,7 +431,7 @@ docker run --rm \ -v "$PWD:/workspace" \ -v "$PWD/socket-config.json:/config.json" \ --env-file .env \ - socket-basics:2.0.3 \ + socket-basics:3.0.0 \ --workspace /workspace \ --config /config.json ``` @@ -455,7 +455,7 @@ for PROJECT in "${PROJECTS[@]}"; do docker run --rm \ -v "$PROJECT:/workspace" \ --env-file .env \ - socket-basics:2.0.3 \ + socket-basics:3.0.0 \ --workspace /workspace \ --all-languages \ --secrets \ @@ -479,7 +479,7 @@ pipeline { stage('Security Scan') { steps { script { - docker.image('ghcr.io/socketdev/socket-basics:2.0.3').inside( + docker.image('ghcr.io/socketdev/socket-basics:3.0.0').inside( "-v ${WORKSPACE}:/workspace --env-file .env" ) { sh ''' @@ -501,7 +501,7 @@ pipeline { ```yaml security-scan: - image: ghcr.io/socketdev/socket-basics:2.0.3 + image: ghcr.io/socketdev/socket-basics:3.0.0 stage: test script: - socket-basics @@ -527,7 +527,7 @@ security-scan: docker run --rm \ -v "$PWD:/workspace" \ --user "$(id -u):$(id -g)" \ - socket-basics:2.0.3 \ + socket-basics:3.0.0 \ --workspace /workspace ``` @@ -546,14 +546,14 @@ security-scan: ```bash docker run --rm \ -v "$(pwd):/workspace" \ # Use $(pwd) instead of $PWD - socket-basics:2.0.3 + socket-basics:3.0.0 ``` 2. Verify mount: ```bash docker run --rm \ -v "$PWD:/workspace" \ - socket-basics:2.0.3 \ + socket-basics:3.0.0 \ ls -la /workspace ``` @@ -583,7 +583,7 @@ security-scan: docker run --rm \ -v "$PWD:/workspace" \ --env-file "$(pwd)/.env" \ - socket-basics:2.0.3 + socket-basics:3.0.0 ``` ### Container Image Too Large @@ -614,7 +614,7 @@ security-scan: ```bash docker run --rm \ -v "$PWD:/workspace" \ - socket-basics:2.0.3 \ + socket-basics:3.0.0 \ --workspace /workspace \ --python \ --secrets \ @@ -635,7 +635,7 @@ security-scan: ```bash docker run --rm \ -v "$PWD:/workspace" \ - socket-basics:2.0.3 \ + socket-basics:3.0.0 \ --workspace /workspace \ --output /workspace/results.json # Save to mounted directory ``` @@ -646,7 +646,7 @@ security-scan: docker run --rm \ -v "$PWD:/workspace" \ -v "$PWD/results:/results" \ - socket-basics:2.0.3 \ + socket-basics:3.0.0 \ --workspace /workspace \ --output /results/scan.json ``` @@ -657,7 +657,7 @@ Add these to your `~/.bashrc` or `~/.zshrc` for quick access: ```bash # Socket Basics Docker aliases -alias sb-docker='docker run --rm -v "$PWD:/workspace" --env-file .env ghcr.io/socketdev/socket-basics:2.0.3 --workspace /workspace' +alias sb-docker='docker run --rm -v "$PWD:/workspace" --env-file .env ghcr.io/socketdev/socket-basics:3.0.0 --workspace /workspace' alias sb-quick='sb-docker --secrets --console-tabular-enabled' alias sb-python='sb-docker --python --secrets --console-tabular-enabled' alias sb-js='sb-docker --javascript --secrets --console-tabular-enabled' @@ -682,7 +682,7 @@ sb-all ## Best Practices 1. **Use pre-built images** โ€” Pull `ghcr.io/socketdev/socket-basics:` instead of building locally -2. **Pin to a specific version** โ€” Avoid `:latest` in production CI; pin to `2.0.3` and upgrade deliberately +2. **Pin to a specific version** โ€” Avoid `:latest` in production CI; pin to `3.0.0` and upgrade deliberately 3. **Use Dependabot** โ€” Reference the image in your Dockerfile/Compose to get automatic upgrade PRs 4. **Inspect baked-in labels** โ€” Run `docker inspect | jq '.[0].Config.Labels'` to verify tool versions 5. **Use .env files** โ€” Keep credentials out of command history @@ -701,7 +701,7 @@ set -e # Configuration PROJECT_DIR="$(pwd)" RESULTS_DIR="./scan-results" -IMAGE_NAME="socket-basics:2.0.3" +IMAGE_NAME="socket-basics:3.0.0" ENV_FILE=".env" # Create results directory diff --git a/docs/pre-commit-hook.md b/docs/pre-commit-hook.md index 585c8f9..03d682b 100644 --- a/docs/pre-commit-hook.md +++ b/docs/pre-commit-hook.md @@ -35,7 +35,7 @@ Best for: Teams wanting consistent environments without installing security tool ```bash # Pull the pre-built image (no build step required) -docker pull ghcr.io/socketdev/socket-basics:2.0.3 +docker pull ghcr.io/socketdev/socket-basics:3.0.0 ``` **2. Create pre-commit hook:** diff --git a/scripts/check_release_docs.py b/scripts/check_release_docs.py new file mode 100644 index 0000000..e49a22c --- /dev/null +++ b/scripts/check_release_docs.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python3 +"""Check or update current Socket Basics release references in documentation.""" + +from __future__ import annotations + +import argparse +import re +import sys +import tomllib +from dataclasses import dataclass +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parent.parent +PYPROJECT_PATH = REPO_ROOT / "pyproject.toml" +DOC_PATHS = (REPO_ROOT / "README.md", *sorted((REPO_ROOT / "docs").rglob("*.md"))) +SEMVER = r"\d+\.\d+\.\d+" + +# Match only references that describe the current Socket Basics release. Other +# versions in these guides (scanner versions, vulnerable Trivy versions, pinned +# third-party Actions, and so on) must remain independent. +REFERENCE_PATTERNS = ( + re.compile(rf"SocketDev/socket-basics@v?(?P{SEMVER})"), + re.compile(rf"SocketDev/socket-basics@\s+# v(?P{SEMVER})"), + re.compile( + rf"(?:(?:ghcr\.io|docker\.io)/socketdev/|socketdev/)?" + rf"socket-basics:(?P{SEMVER})" + ), + re.compile(rf"github\.com/SocketDev/socket-basics refs/tags/v(?P{SEMVER})"), + re.compile(rf"specific version tag \(e\.g\. `@v(?P{SEMVER})`\)"), + re.compile(rf"Version tags\*\* \(`@v(?P{SEMVER})`\)"), + re.compile(rf"\| `@v(?P{SEMVER})` \+ Dependabot"), + re.compile(rf"keeping the `# v(?P{SEMVER})` comment"), + re.compile(rf"exact version such as `(?P{SEMVER})`"), + re.compile(rf'org\.opencontainers\.image\.version": "(?P{SEMVER})"'), + re.compile(rf"pin to `(?P{SEMVER})` and upgrade"), +) + + +@dataclass(frozen=True) +class Reference: + start: int + end: int + version: str + + +def read_canonical_version() -> str: + return tomllib.loads(PYPROJECT_PATH.read_text())["project"]["version"] + + +def find_references(content: str) -> list[Reference]: + references: set[Reference] = set() + for pattern in REFERENCE_PATTERNS: + for match in pattern.finditer(content): + references.add( + Reference( + start=match.start("version"), + end=match.end("version"), + version=match.group("version"), + ) + ) + return sorted(references, key=lambda reference: reference.start) + + +def render_content(content: str, version: str) -> tuple[str, int]: + references = find_references(content) + updated = content + changed = 0 + for reference in reversed(references): + if reference.version == version: + continue + updated = updated[: reference.start] + version + updated[reference.end :] + changed += 1 + return updated, changed + + +def check_docs(version: str) -> list[str]: + mismatches: list[str] = [] + for path in DOC_PATHS: + content = path.read_text() + for reference in find_references(content): + if reference.version == version: + continue + line = content.count("\n", 0, reference.start) + 1 + mismatches.append( + f"{path.relative_to(REPO_ROOT)}:{line} references " + f"{reference.version}; expected {version}" + ) + return mismatches + + +def write_docs(version: str, dry_run: bool) -> int: + total = 0 + for path in DOC_PATHS: + content = path.read_text() + updated, changed = render_content(content, version) + total += changed + if changed and not dry_run: + path.write_text(updated) + if changed: + print(f"{path.relative_to(REPO_ROOT)}: {changed} reference(s) updated") + return total + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description="Keep current Socket Basics release references aligned with pyproject.toml" + ) + mode = parser.add_mutually_exclusive_group(required=True) + mode.add_argument("--check", action="store_true", help="fail on stale references") + mode.add_argument("--write", action="store_true", help="update stale references") + parser.add_argument( + "--version", + help="target version (defaults to the canonical pyproject.toml version)", + ) + parser.add_argument( + "--dry-run", + action="store_true", + help="show updates without writing (only valid with --write)", + ) + args = parser.parse_args() + if args.dry_run and not args.write: + parser.error("--dry-run requires --write") + if args.version and not re.fullmatch(SEMVER, args.version): + parser.error("--version must be X.Y.Z") + return args + + +def main() -> int: + args = parse_args() + version = args.version or read_canonical_version() + if args.write: + changed = write_docs(version, args.dry_run) + action = "Would update" if args.dry_run else "Updated" + print(f"{action} {changed} current-release reference(s) to {version}") + return 0 + + mismatches = check_docs(version) + if mismatches: + print(f"Current-release documentation is out of sync with {version}:") + for mismatch in mismatches: + print(f" - {mismatch}") + print("Run: python3 scripts/check_release_docs.py --write") + return 1 + + print(f"Current-release documentation is in sync: {version}") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/prep_release.py b/scripts/prep_release.py index 8208f53..d02b010 100755 --- a/scripts/prep_release.py +++ b/scripts/prep_release.py @@ -4,10 +4,9 @@ Feature PRs never touch version files; they only add CHANGELOG entries under [Unreleased]. When a release batch is complete, run this once on a fresh -branch: it bumps every version-bearing file and stamps the [Unreleased] -changelog section, so the release PR is a mechanical five-file diff. Tag the -release PR's merge commit and the publish workflow's version gate passes by -construction. +branch: it bumps every version-bearing file, synchronizes current-release docs, +and stamps the [Unreleased] changelog section. Tag the release PR's merge commit +and the publish workflow's version gate passes by construction. Files updated: pyproject.toml [project] version (canonical source) @@ -16,6 +15,8 @@ action.yml derived via sync_release_version.py uv.lock project entry (via `uv lock`) CHANGELOG.md [Unreleased] -> [X.Y.Z] - YYYY-MM-DD + README.md current-release examples + docs/**/*.md matching current-release examples Usage: python scripts/prep_release.py --version 2.2.0 @@ -87,6 +88,19 @@ def _refresh_lock(dry_run: bool) -> None: sys.exit(f"error: `uv lock` failed:\n{exc.stderr}") +def _sync_release_docs(version: str, dry_run: bool) -> None: + command = [ + sys.executable, + str(ROOT / "scripts" / "check_release_docs.py"), + "--write", + "--version", + version, + ] + if dry_run: + command.append("--dry-run") + subprocess.run(command, cwd=ROOT, check=True) + + def main() -> None: parser = argparse.ArgumentParser(description="Prepare version bumps and changelog for a release PR.") parser.add_argument("--version", required=True, help="Release version without v prefix, e.g. 2.2.0") @@ -118,6 +132,8 @@ def main() -> None: subprocess.run([sys.executable, str(ROOT / "scripts" / "sync_release_version.py"), "--write"], cwd=ROOT, check=True) + _sync_release_docs(args.version, args.dry_run) + if not args.dry_run: CHANGELOG.write_text(changelog_content) print(f"CHANGELOG.md: [Unreleased] -> [{args.version}] - {args.date}") @@ -126,7 +142,10 @@ def main() -> None: if not args.dry_run: print("uv.lock: refreshed") - print("\nNext steps: commit these changes on a release branch, open the release PR,") + print( + "\nNext steps: review the synchronized docs, commit these changes on a release " + "branch, and open the release PR," + ) print(f"merge it last, then tag the merge commit as v{args.version} to trigger publish.") diff --git a/tests/test_release_docs.py b/tests/test_release_docs.py new file mode 100644 index 0000000..d9985ee --- /dev/null +++ b/tests/test_release_docs.py @@ -0,0 +1,63 @@ +import tomllib + +from scripts import check_release_docs, prep_release + + +def test_current_release_references_match_canonical_version() -> None: + version = check_release_docs.read_canonical_version() + covered_paths = set() + + for path in check_release_docs.DOC_PATHS: + references = check_release_docs.find_references(path.read_text()) + if not references: + continue + covered_paths.add(path.relative_to(check_release_docs.REPO_ROOT).as_posix()) + assert {reference.version for reference in references} == {version} + + assert { + "README.md", + "docs/github-action.md", + "docs/local-install-docker.md", + "docs/pre-commit-hook.md", + } <= covered_paths + + +def test_render_content_updates_only_socket_basics_release_references() -> None: + content = """\ +uses: SocketDev/socket-basics@v2.0.3 +image: ghcr.io/socketdev/socket-basics:2.0.3 +uses: actions/checkout@abc123 # v6.0.2 +uses: aquasecurity/trivy-action@v0.35.0 +TRIVY_VERSION=0.73.0 +""" + + rendered, changed = check_release_docs.render_content(content, "3.0.0") + + assert changed == 2 + assert "SocketDev/socket-basics@v3.0.0" in rendered + assert "ghcr.io/socketdev/socket-basics:3.0.0" in rendered + assert "actions/checkout@abc123 # v6.0.2" in rendered + assert "aquasecurity/trivy-action@v0.35.0" in rendered + assert "TRIVY_VERSION=0.73.0" in rendered + + +def test_canonical_version_matches_pyproject() -> None: + pyproject = tomllib.loads(check_release_docs.PYPROJECT_PATH.read_text()) + + assert check_release_docs.read_canonical_version() == pyproject["project"]["version"] + + +def test_release_prep_invokes_docs_sync_in_dry_run(monkeypatch) -> None: + calls = [] + + def fake_run(command, *, cwd, check): + calls.append((command, cwd, check)) + + monkeypatch.setattr(prep_release.subprocess, "run", fake_run) + + prep_release._sync_release_docs("3.1.0", dry_run=True) + + command, cwd, check = calls[0] + assert command[-4:] == ["--write", "--version", "3.1.0", "--dry-run"] + assert cwd == prep_release.ROOT + assert check is True