{"id":15472272,"url":"https://github.com/sethmlarson/python-slsa-release-test","last_synced_at":"2025-09-08T23:36:24.724Z","repository":{"id":53846435,"uuid":"521661887","full_name":"sethmlarson/python-slsa-release-test","owner":"sethmlarson","description":null,"archived":false,"fork":false,"pushed_at":"2022-09-14T17:54:54.000Z","size":56,"stargazers_count":0,"open_issues_count":5,"forks_count":1,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-01-18T16:20:35.622Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"cc0-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sethmlarson.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null}},"created_at":"2022-08-05T14:08:26.000Z","updated_at":"2022-08-05T14:09:13.000Z","dependencies_parsed_at":"2023-01-18T07:45:53.851Z","dependency_job_id":null,"html_url":"https://github.com/sethmlarson/python-slsa-release-test","commit_stats":null,"previous_names":[],"tags_count":17,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sethmlarson%2Fpython-slsa-release-test","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sethmlarson%2Fpython-slsa-release-test/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sethmlarson%2Fpython-slsa-release-test/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sethmlarson%2Fpython-slsa-release-test/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sethmlarson","download_url":"https://codeload.github.com/sethmlarson/python-slsa-release-test/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243165537,"owners_count":20246725,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-02T02:30:36.510Z","updated_at":"2025-03-12T06:16:09.427Z","avatar_url":"https://github.com/sethmlarson.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Secure Python package template\n\nTemplate for a Python package with a secure\nproject host and package repository configuration.\n\nThe goals of this project are to:\n\n- Show how to configure a Python package hosted on GitHub with:\n  - Operational security best-practices\n  - Automated publishing to PyPI\n  - Code quality and vulnerability scanning\n  - Build reproducibility\n  - Signed releases\n- Obtain a perfect rating from [OpenSSF Scorecard](https://github.com/ossf/scorecard)\n- Integrate with [Sigstore](https://www.sigstore.dev/) for signed releases\n\n## Creating the GitHub repository\n\nClone this repository locally:\n\n```sh\n$ git clone ssh://git@github.com/sethmlarson/secure-python-package-template\n\nCloning into 'secure-python-package-template'...\n...\nReceiving objects: 100% (79/79), 29.37 KiB | 1002.00 KiB/s, done.\nResolving deltas: 100% (20/20), done.\n```\n\nRename the folder to the name of the package and remove existing git repository:\n\n```sh\n$ mv secure-python-package-template package-name\n$ cd package-name\n$ rm -rf .git\n```\n\nCreate a new git repository and ensure the branch name is `main`:\n\n```sh\n$ git init\nInitialized empty Git repository in .../package-name/.git/\n\n$ git status\nOn branch main\n\nNo commits yet\n...\n```\n\nIf the branch isn't named `main` you can rename the branch:\n\n```sh\n$ git branch -m master main\n```\n\nCreate an **empty** repository on GitHub. To ensure the repository is empty you shouldn't add a README file, .gitignore file, or a license yet. For the examples below the GitHub repository will be named `sethmlarson/package-name` but you should substitute that with the GitHub repository name you chose.\n\nWe need to tell our git repository about our new GitHub repository:\n\n```sh\n$ git remote add origin ssh://git@github.com/sethmlarson/package-name\n```\n\nChange all the names and URLs be for your own package. Places to update include:\n\n- `README.md`\n- `pyproject.toml` (`project.name` and `project.urls.Home`)\n- `src/{{secure_package_template}}`\n- `tests/test_{{secure_package_template}}.py`\n\nYou should also change the license to the one you want to use for the package. Update the value in here:\n\n- `LICENSE`\n- `README.md`\n\nNow we can create our initial commit and ensure it is signed by default:\n\n```sh\n$ git add .\n\n$ git commit -m \"Initial commit\"\n\n# Verify that this commit is signed. If not you\n# should configure git to auto-sign commits.\n$ git verify-commit HEAD\ngpg: Signature made Fri 15 Jul 2022 10:55:10 AM CDT\ngpg:                using RSA key 9B2E1343B0B201B8883C79E3A99A0A21AD478212\ngpg: Good signature from \"Seth Michael Larson \u003csethmichaellarson@gmail.com\u003e\" [ultimate]\n```\n\nNow we push our commit and branch:\n\n```sh\n$ git push origin main\n\nEnumerating objects: 25, done.\nCounting objects: 100% (25/25), done.\nDelta compression using up to 12 threads\nCompressing objects: 100% (21/21), done.\nWriting objects: 100% (25/25), 17.92 KiB | 1.28 MiB/s, done.\nTotal 25 (delta 0), reused 0 (delta 0), pack-reused 0\nTo ssh://github.com/sethmlarson/package-name\n * [new branch]      main -\u003e main\n```\n\nSuccess! You should now see the commit and all files on your GitHub repository.\n\n## Configuring PyPI\n\nPyPI is increasing the minimum requirements for account security and credential management to make consuming packages on PyPI more secure. This includes [eventually requiring 2FA for all users and requiring API tokens to publish packages](https://pyfound.blogspot.com/2020/01/start-using-2fa-and-api-tokens-on-pypi.html). Instead of waiting for these best practices to become required we can opt-in to them now.\n\n### Obtain an API token\n\nAPI tokens will eventually be required for all packages to publish to PyPI.\n\n- Upload a dummy v0.0 package under the desired package name using your PyPI username and password.\n- Create an API token that is scoped to only the package\n- Copy the value into your clipboard, it will be used later (see `PYPI_TOKEN` in the GitHub Environments section below)\n\n### Opt-in to required 2FA\n\nIf you don't have 2FA enabled on PyPI already there's a section in the [PyPI Help page](https://pypi.org/help) about how to enable 2FA for your account. To make 2FA required for the new project:\n\n- Open \"Your projects\" on PyPI\n- Select \"Manage\" for the project\n- Settings \u003e Enable 2FA requirement for project\n\n## Configuring the GitHub repository\n\n### Dependabot\n\n- Settings \u003e Code security and analysis\n- Dependency graph should be enabled. This is the default for public repos.\n- Enable Dependabot security updates\n\n### CodeQL and vulnerable code scanning\n\n- CodeQL is already configured in `.github/workflows/codeql-analysis.yml`\n- Configure as desired after reading the [documentation for CodeQL](https://codeql.github.com/docs).\n\n### Protected branches\n\n- Settings \u003e Branches\n- Select the \"Add rule\" button\n- Branch name pattern should be your default branch, usually `main`\n- Enable \"Require a pull request before merging\"\n  - Enable \"Require approvals\". To get a perfect score from OpenSSF scorecard metric \"[Branch Protection](https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection)\" you must set the number of required reviewers to 2 or more.\n  - Enable \"Dismiss stale pull request approvals when new commits are pushed\"\n  - Enable \"Require review from Code Owners\"\n- Enable \"Require status checks to pass before merging\"\n  - Add all status checks that should be required. For this template they will be:\n    - `Analyze (python)`\n    - `Test (3.8)`\n    - `Test (3.9)`\n    - `Test (3.10)`\n  - Ensure the \"source\" of all status checks makes sense and isn't set to \"Any source\".\n    By default this should be configured properly to \"GitHub Actions\" for all the above status checks.\n  - Enable \"Require branches to be up to date before merging\". **Warning: This will increase the difficulty to receive contributions from new contributors.**\n- Enable \"Require signed commits\". **Warning: This will increase the difficulty to receive contributions from new contributors.**\n- Enable \"Require linear history\"\n- Enable \"Include administrators\". This setting is more a reminder and doesn't prevent administrators from temporarily disabling this setting in order to merge a stuck PR in a pinch.\n- Ensure that \"Allow force pushes\" is disabled.\n- Ensure that \"Allow deletions\" is disabled.\n- Select the \"Create\" button.\n\n### Protected tags\n\n- Settings \u003e Tags \u003e New rule\n- Use a pattern of `*` to protect all tags\n- Select \"Add rule\"\n\n### Publish GitHub Environment\n\n- Settings \u003e Environments \u003e New Environment\n- Name the environment: `publish`\n- Add required reviewers, should be maintainers\n- Select \"Save protection rules\" button\n- Select \"Protected Branches\" in the deployment branches dropdown\n- Select \"Add secret\" in the environment secrets section\n- Add the PyPI API token value under `PYPI_TOKEN`\n\n## Verifying configurations\n\n### Verifying reproducible builds\n\n- Find the latest release that was done via the publish GitHub Environment. ([v0.1.0](https://github.com/sethmlarson/python-package-template/runs/7163956796?check_suite_focus=true))\n- Pull up the [release page on PyPI](https://pypi.org/project/secure-package-template/0.1.0).\n- Select the \"[Download files](https://pypi.org/project/secure-package-template/0.1.0/#files)\" tab.\n- For each `.whl` file select \"view hashes\" and copy the SHA256 and save the value somewhere (`de58d65d34fe9548b14b82976b033b50e55840324053b5501073cb98155fc8af`)\n- Clone the GitHub repository locally. Don't use an existing clone of the repository to avoid tainting the workspace (`$ git clone ssh://git@github.com/sethmlarson/secure-python-package-template`)\n- Check out the corresponding git tag (`$ git checkout v0.1.0`)\n- Run `$ git log -1 --pretty=%ct` and store this value (`1656789393`)\n- Export the stored value into `SOURCE_DATE_EPOCH` (`$ export SOURCE_DATE_EPOCH=1656789393`)\n- Install the dependencies for publishing (`$ python -m pip install -r requirements/publish.txt`)\n- Run `$ python -m build`\n- Run `sha256sum dist/*.whl`\n- Compare SHA256 hashes with the values on PyPI. They should match for each `.whl` file.\n\n## License\n\nCC0-1.0\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsethmlarson%2Fpython-slsa-release-test","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsethmlarson%2Fpython-slsa-release-test","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsethmlarson%2Fpython-slsa-release-test/lists"}