Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 76 additions & 4 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,81 @@
# Dependabot: keep GitHub Actions on the latest versions automatically.
#
# Repo convention: pin every action to the latest major (checkout@v7,
# upload-artifact@v7, ...). Watching upstream releases by hand leaks; Dependabot
# folds stale pins into one grouped PR per week, and merges are backstopped by
# the normal test suite. Major jumps like v7 -> v8 arrive in the same stream.
# Repo convention: pin every external action to a full 40-character commit
# SHA, with the release tag in a trailing comment
# (actions/checkout@<sha> # v7.0.1). A mutable tag is a supply-chain
# liability: whoever controls the action's repository can repoint the tag at
# malicious code, and our workflows hold a token that can write to the
# public Homebrew tap that users install from. Pinning to a SHA makes the
# reference immutable, which is what GitHub's own hardening guide calls the
# most secure option, and what the OpenSSF Scorecard Pinned-Dependencies
# check measures.
#
# The local reusable-workflow call (uses: ./.github/workflows/update-tap.yml)
# is deliberately left unpinned: it is a path inside this repository, so
# there is no third-party source to pin, and GitHub requires local
# references to stay as paths.
#
# Why Dependabot still matters with SHA pins: Dependabot resolves the
# pinned action's latest release and opens a PR that rewrites the SHA and
# the trailing comment. Watching upstream releases by hand leaks; Dependabot
# folds stale pins into one grouped PR per week, and merges are backstopped
# by the normal test suite. Major bumps like v7 -> v8 arrive in the same
# stream.
#
# The known cost of SHA pinning, stated plainly so nobody rediscovers it:
# Dependabot ALERTS are only generated for actions referenced with semantic
# versioning, not SHA versioning. So after this convention lands, a
# compromised release of a pinned action arrives as a version-update PR
# rather than as a security alert, and dependabot_security_updates will
# have nothing to open for the github-actions ecosystem. That is the
# intended trade: a version-update PR still lands the fix, and the
# alternative (a mutable tag) means a retagged release reaches this repo
# without any PR at all.
#
# No alert channel remains for the action pins themselves. codeql.yml
# analyzes C++ only, not workflow files, so it is not a substitute here
# either. The version-update PR below is the only place a compromised
# action release would be seen, which is why the merge procedure below
# treats reading that diff as mandatory.
#
# Groups: one group for the whole ecosystem, so a week with several stale
# pins is one PR to review rather than one per action.
#
# MERGING A DEPENDABOT PR (the procedure, since it is not the obvious one):
#
# 1. Use the MERGE COMMIT strategy. Squash and rebase both put a commit on
# main that is authored by dependabot[bot], and a workflow on such a
# commit gets a read-only GITHUB_TOKEN, so the CodeQL SARIF upload fails
# with "403: Resource not accessible by integration". codeql.yml skips
# that upload so the run stays green, but a merge commit keeps publishing
# results.
#
# 2. The PR arrives with zero reviews and is BLOCKED on "review required".
# That is expected: Dependabot cannot approve its own PR. You bypass it
# the same way you bypass your own PRs, because the "Require review"
# ruleset lists the admin role as a bypass actor:
# gh api repos/justsrc/TetherKit/pulls/<PR>/merge -X PUT -f merge_method=merge
# The REST endpoint makes the mechanism explicit. `gh pr merge` refuses
# at its own pre-flight when the PR is BLOCKED (cli/cli#13388, still
# open) and suggests --auto or --admin; --admin only suppresses that
# client-side refusal (no admin flag travels to the server, and the
# "Protect main" ruleset has no bypass actor at all, so nothing can
# override the required status checks). What merges the PR is your role:
# the "Require review" ruleset lists the admin role as a bypass actor
# with bypass_mode pull_request.
#
# 3. Read the diff before merging. The whole point of the SHA convention is
# that a new action version is a decision, not an automatic consequence:
# a version-update PR is the last human checkpoint before a new release of
# a third-party action can run in workflows that hold the tap token.
# A compromised release of a pinned action arrives here as an ordinary
# version-update PR precisely because SHA pinning kills the alert channel
# (see above), so this review is the only place that compromise would be
# caught.
#
# 4. Do NOT add Dependabot to the bypass list of any ruleset: that would
# let the bot merge its own PR unreviewed, which is exactly what the
# SHA pinning above exists to prevent.

version: 2
updates:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/bench.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
runs-on: macos-26
timeout-minutes: 30
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Install libusb
run: brew install libusb
Expand All @@ -46,7 +46,7 @@ jobs:
./build-rel/bin/tetherkit_bench | tee bench-report.md

- name: Upload the bench report
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: tetherkit-bench-${{ github.run_number }}
path: bench-report.md
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jobs:
DISPATCHED_BUILD_TYPE: ${{ inputs.build_type }}

steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Install libusb
run: brew install libusb
Expand Down Expand Up @@ -116,18 +116,18 @@ jobs:
name="TetherKit-${GITHUB_RUN_NUMBER}-${DISPATCHED_OS}-${DISPATCHED_BUILD_TYPE}.zip"
ditto -c -k --keepParent dist/TetherKit.app "$name"

# upload-artifact@v7 supports single-file direct upload (archive: false):
# upload-artifact v7 supports single-file direct upload (archive: false):
# the downloaded artifact IS the ditto zip above, not a zip inside another
# zip. With archive: false the name input is ignored (the file name wins).
- name: Upload TetherKit.app artifact
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
path: TetherKit-${{ github.run_number }}-${{ env.DISPATCHED_OS }}-${{ env.DISPATCHED_BUILD_TYPE }}.zip
archive: false
retention-days: 14

- name: Upload libtetherkit shared-library artifact
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: tetherkit-libs-${{ github.run_number }}-${{ env.DISPATCHED_OS }}-${{ env.DISPATCHED_BUILD_TYPE }}
path: build/lib/libtetherkit*.dylib
Expand Down
128 changes: 106 additions & 22 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,38 @@
# in the libusb transfer pool, the BPF buffer walk, and the
# reinterpret_cast-heavy private ABI bridge are exactly its turf.
#
# third_party/doctest stays in the analysis set on purpose: paths-ignore is
# inert for C/C++ manual builds (github/codeql-action#3890; the docs confine
# paths filtering to build-mode none, and the workflow would warn about it at
# run time). SARIF post-filtering means a new third-party action for exactly
# one benign vendored-code alert (cpp/use-after-free at
# third_party/doctest/doctest.h:3710). Accepted as baseline noise instead.
#
# Free for public repos. Must run on macOS: CMakeLists fatals on non-Apple
# platforms.
#
# pull_request trigger restricted to workflow edits (paths:
# ['.github/workflows/**']): running a many-minute C++ build on every PR push
# isn't worth it, but workflow changes self-verify before landing. dependabot.yml
# and .github/scripts/ edits never match this filter; note it only gates
# pull_request runs, pushes to main still analyze them. Dependabot's weekly
# grouped action-bump PR deliberately rides along because it edits workflow
# files. The concurrency group below is event-scoped (the tests.yml doctrine):
# Merging a Dependabot PR (how this interacts with the read-only token):
# Prefer the MERGE COMMIT strategy. Squash and rebase both produce a main
# tip authored by dependabot[bot], and any workflow running on such a
# commit gets a read-only GITHUB_TOKEN, so the SARIF upload 403s.
# A merge commit is authored by the human merger and keeps write scope.
# The analyze step below also skips the upload for a Dependabot-authored
# push, so the run stays green either way, but the merge-commit path is the
# one that keeps publishing results.
# The review requirement on the bot PR is bypassed BY YOU, the same way it
# is for your own PRs: the "Require review" ruleset lists the admin role as
# a bypass actor with bypass_mode pull_request, so
# gh api repos/justsrc/TetherKit/pulls/<PR>/merge -X PUT -f merge_method=merge
# engages the bypass. (`gh pr merge` refuses at its own pre-flight when
# mergeStateStatus is BLOCKED; that is cli/cli#13388, still open. The REST
# endpoint does the right thing.) Do NOT add Dependabot to the bypass list:
# that would let the bot merge its own PR unreviewed, which is exactly what
# the SHA pinning below exists to prevent.
#
# pull_request runs on every PR, not only on workflow edits. The old
# paths-filtered trigger was a cost decision (a many-minute C++ build per PR
# push) taken when the alert list was empty. It is now the wrong trade: the
# check's whole value is catching a defect BEFORE it lands, and the defects
# worth catching live in src/, not in .github/workflows/. With the filter, a
# pull_request run saw almost nothing, so "C++ analysis" could not be a
# required status check without deadlocking PRs that never trigger it. macOS
# minutes are free for public repos, so the cost is wall-clock on the PR, not
# money.
#
# The concurrency group below is event-scoped (the tests.yml doctrine):
# push-to-main, the Monday cron and pull_request runs sit in different groups
# and never cancel each other; within one event, canceling a superseded run is
# safe because the newer run's SARIF simply replaces its alerts.
Expand All @@ -37,8 +52,6 @@ on:
push:
branches: [main]
pull_request:
paths:
- ".github/workflows/**"
workflow_dispatch:

permissions:
Expand All @@ -61,21 +74,59 @@ jobs:
name: C++ analysis
runs-on: macos-26
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Install libusb
run: brew install libusb

- uses: github/codeql-action/init@v4
- uses: github/codeql-action/init@33d7b85da1a04171e211befe2c739303b9a4c066 # v4.39.3
with:
languages: cpp
# Manual build mode: the build commands come from the next step.
build-mode: manual

- name: Build
run: |
# Tests and benchmarks stay enabled: everything compiles, everything
# lands in the analysis set.
# Tests are OFF, so doctest never enters the analysis set.
#
# Why this mechanism and not the obvious ones:
# - paths / paths-ignore in a CodeQL config file are inert for C++
# in manual build mode. The codeql-action maintainer states it
# directly (github/codeql-action#3890): "When using build-mode:
# autobuild or build-mode: manual, custom steps to ignore
# specified files are indeed still needed, and paths-ignore does
# not work." The docs confine path filtering to build-mode none.
# - SARIF post-filtering (advanced-security/filter-sarif) would work,
# but it is one more action to trust and pin on the analysis
# path, and it would need the workflow to upload the SARIF
# itself rather than letting the action do it. Not worth it for
# one benign vendored alert.
# - What GitHub does document for compiled languages is the right
# answer: "To restrict the analysis of a compiled language, you
# should configure your build process to compile only the
# specified files." For C++ the database contains exactly the
# translation units the build compiled.
#
# So the build decides the analysis set, and this project already
# has the switch: doctest is an INTERFACE library linked only into
# tetherkit_tests (tests/CMakeLists.txt), so with
# TETHERKIT_BUILD_TESTS=OFF no test TU is compiled, doctest.h is
# never parsed, and third_party/doctest never reaches the database.
#
# That retires cpp/use-after-free at third_party/doctest/doctest.h:3710
# at the source instead of dismissing it. That alert is a false
# positive on a union assignment (the buffer is freed and a new one
# is assigned before the read; CodeQL's C++ model cannot see across
# the union member write), and it lives in a vendored test framework
# that never ships. Upstream doctest 2.5.3 carries the identical
# code, so upgrading does not clear it either.
#
# The cost, stated plainly: tests/ drops out of the analysis set
# along with doctest. That is first-party test code, not shipped
# code, and the shipped surface (src/, include/) stays fully
# analyzed -- which is where the BPF buffer walk and the
# reinterpret_cast-heavy private ABI bridge live, and those are
# exactly the findings worth having.
#
# CodeQL's macOS tracer forces traced child processes onto the x86_64
# slice under Rosetta (observed: build defaulted to x86_64; run 42513807606).
Expand All @@ -88,9 +139,42 @@ jobs:
# "simplification" would silently reintroduce the bug.
cmake -S . -B build \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_OSX_ARCHITECTURES=arm64
-DCMAKE_OSX_ARCHITECTURES=arm64 \
-DTETHERKIT_BUILD_TESTS=OFF
cmake --build build -j"$(sysctl -n hw.ncpu)"

- uses: github/codeql-action/analyze@v4
# The upload is skipped for a commit authored by Dependabot.
#
# Why: a Dependabot PR merged with SQUASH produces a commit on main
# that is authored by Dependabot, and any workflow running on such a
# commit gets a read-only GITHUB_TOKEN. The SARIF upload then fails
# with "403: Resource not accessible by integration" (the upload
# library warns and rethrows; the action's outer catch then fails the
# step via core.setFailed, so the run goes red).
# GitHub's own guidance is to merge bot PRs with a MERGE COMMIT instead,
# which keeps the commit authored by the human merger. This guard is
# the belt to that braces: it keeps the analysis running and the run
# green either way.
#
# The pull_request path is NOT affected and must not be guarded. Code
# scanning always allows uploading results when the pull_request event
# triggers the run, because the results are scoped to that one pull
# request. (upload-lib.ts's pull_request branch only fills base_ref and
# base_sha into the upload payload; the permission itself is GitHub's
# service-side policy.) Guarding it would be a regression: no upload
# means no result, and a required check that never reports blocks the
# PR forever (the failure mode documented in cli/cli#13388's
# neighbourhood: a scan that skips itself can never satisfy a rule
# that demands one).
#
# 'never' skips only the upload: the analysis still runs, the summary
# counts are printed into the job log, and the SARIF is written to
# ../results (outside the workspace, not uploaded and not kept as an
# artifact). This is the action's own input for the purpose
# (analyze/action.yml: upload: always | failure-only | never) at the
# pinned v4.39.3.
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@33d7b85da1a04171e211befe2c739303b9a4c066 # v4.39.3
with:
category: "/language:cpp"
upload: ${{ github.event_name == 'push' && github.event.head_commit.author.username == 'dependabot[bot]' && 'never' || 'always' }}
6 changes: 3 additions & 3 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
name: clang-format
runs-on: macos-26
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# fetch-depth 2 provides the HEAD~1 fallback base below; anything older
# (a pull-request base tip, or github.event.before on a batch push) is
# fetched explicitly when missing.
Expand Down Expand Up @@ -99,7 +99,7 @@ jobs:
# continue-on-error to promote this to a hard gate.
continue-on-error: true
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Install libusb
run: brew install libusb
Expand All @@ -126,7 +126,7 @@ jobs:
fi

- name: Upload the clang-tidy log
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
if: always()
with:
name: tetherkit-tidy-log
Expand Down
9 changes: 7 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
runs-on: macos-26

steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

# The tag and CMakeLists' VERSION must agree, otherwise
# `tetherkit-cli --version` will not match the version on the Release /
Expand Down Expand Up @@ -178,5 +178,10 @@ jobs:
tag: ${{ github.ref_name }}
# Narrow mapping instead of secrets: inherit: the callee needs exactly
# one secret, and the caller's remaining secrets stay out of its scope.
#
# HOMEBREW_TAP_TOKEN is the fine-grained token scoped to
# justsrc/homebrew-tap (contents:write for the branch push,
# pull_requests:write for the PR). Declared in the callee's
# workflow_call secrets block, so the name validates at startup.
secrets:
HOMEBREW_TAP_DEPLOY_KEY: ${{ secrets.HOMEBREW_TAP_DEPLOY_KEY }}
HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
Loading
Loading