From bb6c1954d9660998370fee855500d3ec4c98a616 Mon Sep 17 00:00:00 2001 From: DavidHruby1 Date: Sun, 18 Oct 2026 14:22:06 +0200 Subject: [PATCH 1/2] security: harden GitHub automation and repository controls --- .github/CODEOWNERS | 6 +++ .github/workflows/ci.yml | 46 ++++++++++++++++++----- .github/workflows/codeql.yml | 13 +++++-- .github/workflows/release.yml | 15 +++++--- SECURITY.md | 13 +++++++ docs/RELEASES.md | 9 +++-- docs/SECURITY.md | 71 +++++++++++++++++++++++++++++++++++ 7 files changed, 150 insertions(+), 23 deletions(-) create mode 100644 .github/CODEOWNERS create mode 100644 SECURITY.md create mode 100644 docs/SECURITY.md diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..aca0a8e --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,6 @@ +# Security and release changes require the repository owner when protections apply. +/.github/ @DavidHruby1 +/requirements*.txt @DavidHruby1 +/screamer.spec @DavidHruby1 +/SECURITY.md @DavidHruby1 +/docs/SECURITY.md @DavidHruby1 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b320758..1293324 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,11 +15,14 @@ permissions: jobs: # Every application module must byte-compile and import on a non-Windows host. compile: + timeout-minutes: 15 name: compile (ubuntu / py3.12) runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 - - uses: actions/setup-python@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.12" - run: python -m compileall src/ tests/ .github/scripts/ @@ -44,6 +47,7 @@ jobs: # Full test suite on the target platform. test: + timeout-minutes: 30 name: test (windows / py${{ matrix.python-version }}) runs-on: windows-latest strategy: @@ -51,8 +55,10 @@ jobs: matrix: python-version: ["3.11", "3.12"] steps: - - uses: actions/checkout@v7 - - uses: actions/setup-python@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: ${{ matrix.python-version }} cache: pip @@ -68,11 +74,14 @@ jobs: run: python -m unittest discover -s tests -v build: + timeout-minutes: 40 name: build (windows / py3.12) runs-on: windows-latest steps: - - uses: actions/checkout@v7 - - uses: actions/setup-python@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.12" cache: pip @@ -90,11 +99,14 @@ jobs: } lint: + timeout-minutes: 10 name: lint (ruff) runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 - - uses: actions/setup-python@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.12" - run: pip install ruff==0.15.15 @@ -102,12 +114,26 @@ jobs: - run: ruff format --check src/ tests/ .github/scripts/ audit: + timeout-minutes: 15 name: pip-audit (CVE scan) runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 - - uses: actions/setup-python@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.12" - run: pip install pip-audit - run: pip-audit -r requirements.txt -r requirements-build.txt + + dependency-review: + name: dependency-review + if: ${{ github.event_name == 'pull_request' }} + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0 + with: + fail-on-severity: low + comment-summary-in-pr: never diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 3bad13b..aca38db 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -10,17 +10,22 @@ on: permissions: contents: read - security-events: write jobs: analyze: + timeout-minutes: 30 name: analyze (python) + permissions: + contents: read + security-events: write runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 - - uses: github/codeql-action/init@v3 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: github/codeql-action/init@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4 with: languages: python - - uses: github/codeql-action/analyze@v3 + - uses: github/codeql-action/analyze@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4 with: category: "/language:python" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4d19d2f..634fd1f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,6 +15,7 @@ permissions: jobs: plan: + timeout-minutes: 20 if: >- ${{ github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'push' && @@ -28,7 +29,7 @@ jobs: tag: ${{ steps.plan.outputs.tag }} sha: ${{ steps.plan.outputs.sha }} steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ github.event.workflow_run.head_sha }} fetch-depth: 0 @@ -41,17 +42,18 @@ jobs: run: python .github/scripts/release.py plan build: + timeout-minutes: 40 needs: plan if: ${{ needs.plan.outputs.tag != '' }} runs-on: windows-latest permissions: contents: read steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ needs.plan.outputs.sha }} persist-credentials: false - - uses: actions/setup-python@v7 + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: '3.12' cache: pip @@ -77,7 +79,7 @@ jobs: "$hash $zip" | Out-File -FilePath "$zip.sha256" -Encoding ascii "ASSET_ZIP=$zip" | Out-File -FilePath $env:GITHUB_ENV -Append "ASSET_SHA=$zip.sha256" | Out-File -FilePath $env:GITHUB_ENV -Append - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: release-assets path: | @@ -87,16 +89,17 @@ jobs: retention-days: 7 publish: + timeout-minutes: 10 needs: [plan, build] runs-on: ubuntu-latest permissions: contents: write steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ needs.plan.outputs.sha }} persist-credentials: false - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 with: name: release-assets path: release-assets diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..f8df98f --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,13 @@ +# Security Policy + +Security fixes are maintained for the latest published release. Update to the latest +release before reporting a vulnerability. + +Report vulnerabilities privately through [GitHub private vulnerability reporting](https://github.com/DavidHruby1/screamer/security/advisories/new). +Include the affected version, impact and a minimal reproduction. Do not put API keys, +recordings, transcripts or other private data into public issues or pull requests. +If a credential has been exposed, revoke it at its provider immediately; deleting a +file or commit does not revoke the credential. + +Repository controls and their operational requirements are documented in +[Repository Security](docs/SECURITY.md). diff --git a/docs/RELEASES.md b/docs/RELEASES.md index 45285b4..23e1f00 100644 --- a/docs/RELEASES.md +++ b/docs/RELEASES.md @@ -11,7 +11,9 @@ file; GitHub generates the release notes. The Windows job runs unit tests, builds the onedir executable, and produces the versioned ZIP and `.sha256`. Only a separate publication job receives `contents: write`; it checks the tested SHA, checksum, executable in the ZIP and GitHub asset digests, then -publishes the draft. It never runs dependency installation or the build with write +publishes the draft. New releases become immutable at publication, protecting their +assets and tags from later replacement. Older releases are not locked retroactively. +It never runs dependency installation or the build with write permission. A tag may exist before publication if asset upload fails; this is not a public release. The workflow accepts a matching draft/tag on retry but refuses to overwrite an asset or tag with a mismatched SHA/digest. @@ -36,8 +38,9 @@ planning inspects every paginated release page. release or reuse a version that users may already have downloaded. This is automation after **human review of the code PR**, not automated bypass of -branch protection. There is no signing certificate or supply-chain attestation: a -checksum detects transfer corruption but is not proof of provenance. CI checks the +branch protection. There is no Windows code-signing certificate; the checksum detects transfer corruption +and does not authenticate the desktop executable. GitHub release immutability protects +newly published release assets and tags against later replacement. CI checks the build output exists; it does not run the packaged app with a real microphone. Windows DPAPI, Win32 input and the downloadable ZIP should be smoke-tested on Windows when shipping a significant desktop change. diff --git a/docs/SECURITY.md b/docs/SECURITY.md new file mode 100644 index 0000000..eb72ae5 --- /dev/null +++ b/docs/SECURITY.md @@ -0,0 +1,71 @@ +# Repository Security + +This project is public. Visitors can read and fork it, but cannot push to this +repository without an authorized credential. Only the owner currently has write +access. GitHub account security, authorized apps and the owner's computers remain +part of the trust boundary; repository settings cannot protect a compromised admin. +Use 2FA with a passkey or security key and review authorized apps and SSH keys. + +## Branch protection and administrator access + +`main` requires a pull request, one approving review, dismissal of stale approvals, +resolved conversations, an up-to-date branch and the following GitHub Actions checks: + +- `compile (ubuntu / py3.12)` +- `test (windows / py3.11)` +- `test (windows / py3.12)` +- `build (windows / py3.12)` +- `lint (ruff)` +- `pip-audit (CVE scan)` +- `analyze (python)` +- `dependency-review` + +Required checks are tied to the GitHub Actions app. CODEOWNERS assigns workflow, +release and dependency changes to `@DavidHruby1`; applicable PRs require code owner +review. Force pushes and branch deletion are disabled in the branch rule. + +**Administrator bypass is intentional and must remain enabled.** The owner can merge +their own PRs without another reviewer. Do not enable `enforce_admins`, remove this +exception through a ruleset, or add an environment approval that prevents the owner +from publishing. All checks should still be inspected before an admin merge. + +## GitHub Actions + +- All external fork contributors require approval before their workflows run. + Inspect changes to workflows, dependency files and executable scripts before + approving. Approval permits code execution on GitHub's temporary runner. +- The repository allows GitHub-owned actions only and requires full commit SHA pins. + Every action in the workflow files has an upstream SHA with a version comment. + Dependabot maintains these pins through reviewed PRs. +- The default token is read-only and cannot approve PRs. Checkouts do not persist + credentials. CodeQL receives only its additional security reporting permission; + only the release publication job receives `contents: write`. +- Jobs have explicit time limits. There are no self-hosted runners or stored + repository Actions secrets. +- Dependency review rejects newly introduced dependencies with known vulnerabilities + of low severity or higher. `pip-audit` also checks the resolved Python dependency + set. These checks do not prove that an unknown vulnerability or malicious package + is absent. + +When adding an action, review its upstream source and permissions, pin the full SHA, +and keep the version comment so Dependabot can update it. An older open PR containing +unpinned actions needs to incorporate the updated `main` before its checks can rerun +under the SHA policy. Administrator merge access remains available. + +## Alerts and releases + +Dependabot alerts and security update PRs are enabled independently of weekly version +update PRs. Secret scanning and push protection remain enabled. Review and remediate +alerts; an empty alert list is not proof that no secret or vulnerability exists. +Private vulnerability reporting is enabled; see [Security Policy](../SECURITY.md). + +Future releases are immutable after publication. The release workflow builds from a +successful CI push to this repository's `main`, waits for CodeQL on the same commit, +and gives build jobs no write token. It uploads and checks assets while the release +is a draft, then publishes it. Immutability does not retroactively lock old releases. +See [Release Operations](RELEASES.md) for retry and recovery procedures. + +GitHub repository policies are configured through repository settings and are not +applied by checking out these files. After changing them, read the settings back and +confirm the administrator exception is still present. Do not grant extra token +permissions or disable security checks just to make a dependency update pass. From bd7a662ab7b41155713c68efa7bb9f9a8dd180b5 Mon Sep 17 00:00:00 2001 From: DavidHruby1 Date: Sun, 18 Oct 2026 14:23:28 +0200 Subject: [PATCH 2/2] security: enforce CodeQL findings while preserving admin bypass --- .github/workflows/codeql.yml | 1 + docs/SECURITY.md | 3 +++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index aca38db..a159cc9 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -26,6 +26,7 @@ jobs: - uses: github/codeql-action/init@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4 with: languages: python + queries: security-extended - uses: github/codeql-action/analyze@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4 with: category: "/language:python" diff --git a/docs/SECURITY.md b/docs/SECURITY.md index eb72ae5..d963b2e 100644 --- a/docs/SECURITY.md +++ b/docs/SECURITY.md @@ -23,6 +23,9 @@ resolved conversations, an up-to-date branch and the following GitHub Actions ch Required checks are tied to the GitHub Actions app. CODEOWNERS assigns workflow, release and dependency changes to `@DavidHruby1`; applicable PRs require code owner review. Force pushes and branch deletion are disabled in the branch rule. +A separate CodeQL ruleset rejects PRs with any new CodeQL alert, rather than merely +requiring the analysis job to finish. CodeQL runs the `security-extended` query suite. +That ruleset explicitly allows the administrator role to bypass it at any time. **Administrator bypass is intentional and must remain enabled.** The owner can merge their own PRs without another reviewer. Do not enable `enforce_admins`, remove this