Skip to content
Open
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
24 changes: 24 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Code owners for security-critical paths.
#
# Why this file exists: the repo's rulesets require 1 approval from anyone
# with write access — for a wallet app that is too thin on the paths that
# touch signing ceremonies, key material and the CI gates themselves. With
# "Require review from Code Owners" enabled in the develop/staging/main
# rulesets, the people below become a mandatory second pair of eyes exactly
# where a mistake costs user funds. Until an admin flips that ruleset
# setting, this file still auto-requests their review on matching PRs.

# Signing ceremonies & key handling
/lib/packages/wallet/ @davidleomay @TaprootFreak @konstantinullrich
/lib/packages/hardware_wallet/ @davidleomay @TaprootFreak @konstantinullrich
/lib/screens/hardware_connect_bitbox/ @davidleomay @TaprootFreak @konstantinullrich

# Dependency manifests (supply chain)
/pubspec.yaml @davidleomay @TaprootFreak @konstantinullrich
/pubspec.lock @davidleomay @TaprootFreak @konstantinullrich

# CI and the gates themselves — a gate nobody guards is not a gate
/.github/ @davidleomay @TaprootFreak @konstantinullrich
/.coverage-floor-lines @davidleomay @TaprootFreak @konstantinullrich
/.coverage-floor-functions @davidleomay @TaprootFreak @konstantinullrich
/tool/ @davidleomay @TaprootFreak @konstantinullrich
26 changes: 26 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
## What

<!-- One or two sentences: what does this PR change, and why? -->

## Tiers (docs/testing.md)

- [ ] Tier 0/1 (unit/widget) cover the change — coverage floor holds (CI enforces it)
- [ ] Touches `hardware_wallet`/`wallet` paths → Tier 2 (BitBox simulator) runs on this PR
- [ ] UI flow changed → `tier3:full` label set, or post-merge develop run is sufficient because: <!-- why -->
- [ ] Platform-coupled code without integration test carries `// @no-integration-test: <reason>`

## Goldens & handbook

- [ ] No visual change — baselines untouched
- [ ] Baselines regenerated via `golden-regenerate.yaml` (CI re-runs automatically on the bot commit)

## API authority (CONTRIBUTING.md)

- [ ] No new client-side decision logic — server capability flags consumed as-is
- [ ] Pair-PR with the API repo linked here if a capability is consumed: <!-- link -->

## Security checklist

- [ ] Touches signing, key handling, or `pubspec` → code-owner review requested
- [ ] No secret, seed, or PII logged or persisted outside the secure storage paths
- [ ] New dependency added → justified here: <!-- why this package? -->
35 changes: 35 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Dependency-update automation.
#
# Why: this repo currently has no automated dependency monitoring at all —
# a known-vulnerable transitive package would sit unnoticed until a human
# happens to read an advisory. For a wallet app the dependency tree is the
# single largest attack surface (the 2025 npm supply-chain worms made that
# concrete); pub is smaller but not immune, and GitHub Actions are code we
# execute with repo credentials.
#
# The 7-day cooldown is deliberate: freshly published versions are the
# riskiest (compromised-maintainer releases get yanked within days). Waiting
# a week would have blocked every major 2025 supply-chain incident while
# costing nothing for legitimate updates.

version: 2
updates:
- package-ecosystem: pub
directory: /
schedule:
interval: weekly
day: monday
target-branch: staging
cooldown:
default-days: 7
open-pull-requests-limit: 5

- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
day: monday
target-branch: staging
cooldown:
default-days: 7
open-pull-requests-limit: 5
46 changes: 46 additions & 0 deletions .github/workflows/dependency-review.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Dependency Review

# Blocks PRs that introduce dependencies with known vulnerabilities.
#
# Why: Dependabot (see .github/dependabot.yml) watches the EXISTING tree on a
# schedule; this action gates the PR DIFF itself — a newly added pub package
# or GitHub Action with a known high-severity advisory cannot enter the repo
# in the first place. GitHub's dependency graph covers pub, so Dart
# dependencies are in scope. Zero maintenance, first-party action.

on:
pull_request:
branches-ignore: [main]

permissions:
contents: read

jobs:
dependency-review:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

# The action hard-fails when the repo's Dependency graph is disabled
# (admin setting). Probe first: with the graph off, emit a loud warning
# and skip — with it on, the gate arms itself automatically, no
# follow-up commit needed.
- name: Check Dependency graph availability
id: graph
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
if gh api "repos/${{ github.repository }}/dependency-graph/sbom" --silent >/dev/null 2>&1; then
echo "enabled=true" >> "$GITHUB_OUTPUT"
else
echo "enabled=false" >> "$GITHUB_OUTPUT"
echo "::warning::Dependency graph is disabled for this repository — dependency review SKIPPED. An admin should enable it: https://github.com/${{ github.repository }}/settings/security_analysis"
fi

- uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0
if: steps.graph.outputs.enabled == 'true'
with:
fail-on-severity: high
comment-summary-in-pr: on-failure
26 changes: 19 additions & 7 deletions .github/workflows/golden-regenerate.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,11 @@ concurrency:
group: golden-regenerate-${{ github.ref }}
cancel-in-progress: true

# `contents: write` is load-bearing: the auto-commit step pushes back to
# the dispatched branch. The default `GITHUB_TOKEN` permission is `read`.
# Push happens via the TAG_DEPLOY_KEY ssh-agent (see below), not the
# GITHUB_TOKEN — token pushes would leave the new head SHA without any
# triggered checks. `contents: read` is enough for checkout.
permissions:
contents: write
contents: read

jobs:
regenerate:
Expand All @@ -40,11 +41,22 @@ jobs:
timeout-minutes: 30
steps:
- uses: actions/checkout@v4

# Push via the existing deploy key (same key auto-tag.yaml uses) instead
# of GITHUB_TOKEN. This closes a documented gate hole: pushes made with
# GITHUB_TOKEN deliberately do NOT trigger workflows, so the bot-pushed
# baseline commit used to sit at the PR head with ZERO status checks —
# and GitHub shows such a PR as mergeable. Required checks never ran
# against the new baselines unless a human remembered the empty-commit
# re-arm. Deploy-key pushes DO trigger workflows, so the full PR gate
# re-runs on the regenerated head automatically.
- name: Setup SSH for Deploy Key
uses: webfactory/ssh-agent@v0.9.0
with:
# Explicit token so the later `git push` is authenticated. Without
# this, checkout still works but the remote is configured with no
# credential helper and the push fails with HTTP 403.
token: ${{ secrets.GITHUB_TOKEN }}
ssh-private-key: ${{ secrets.TAG_DEPLOY_KEY }}

- name: Configure Git for SSH
run: git remote set-url origin git@github.com:${{ github.repository }}.git
- uses: subosito/flutter-action@v2
with:
flutter-version: "3.41.6"
Expand Down
82 changes: 82 additions & 0 deletions .github/workflows/pull-request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -364,3 +364,85 @@ jobs:
name: bitbox-audit-report
path: bitbox-audit-report.md
if-no-files-found: warn

# ---------------------------------------------------------------------------
# Coverage Floor Ratchet Guard
#
# docs/testing.md defines the ratchet protocol: lowering a floor file needs
# reviewer sign-off, made visible via the `coverage:lower-floor` label. Until
# now that was convention only — a quiet one-line edit to
# `.coverage-floor-lines` inside a big diff could slip through review and
# silently de-arm the Coverage Floor Gate for every future PR. This job makes
# the protocol mechanical: a floor decrease without the label fails the PR.
# Raising a floor (the normal ratchet direction) passes without ceremony.
floor-ratchet-guard:
name: Coverage Floor Ratchet Guard
if: github.event_name == 'pull_request' && github.event.pull_request.draft == false
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Floor may only decrease with the coverage:lower-floor label
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HAS_LABEL: ${{ contains(github.event.pull_request.labels.*.name, 'coverage:lower-floor') }}
run: |
set -euo pipefail
rc=0
for f in .coverage-floor-lines .coverage-floor-functions; do
BASE="$(git show "$BASE_SHA:$f" 2>/dev/null || echo 0)"
HEAD="$(cat "$f")"
echo "$f: base=$BASE head=$HEAD"
if awk -v a="$HEAD" -v b="$BASE" 'BEGIN { exit !(a < b) }'; then
if [ "$HAS_LABEL" != "true" ]; then
echo "::error::$f was lowered ($BASE -> $HEAD) without the 'coverage:lower-floor' label. Lowering a floor requires explicit reviewer sign-off via the label (docs/testing.md ratchet protocol)."
rc=1
else
echo "$f lowered with label — allowed."
fi
fi
done
exit "$rc"

# ---------------------------------------------------------------------------
# PR Policy
#
# Two cheap review-quality gates:
# - Conventional-commit PR titles keep `git log` greppable and the release
# notes readable (the repo already uses the convention; this just stops
# drift).
# - The size cap exists because review quality degrades sharply with diff
# size — and for a wallet app, an under-reviewed PR is a security risk,
# not a style problem. Generated code (goldens, codegen, pubspec.lock)
# legitimately exceeds the cap: justify it and add the `size:override`
# label.
pr-policy:
name: PR Policy
if: github.event_name == 'pull_request' && github.event.pull_request.draft == false
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Conventional PR title
env:
TITLE: ${{ github.event.pull_request.title }}
run: |
set -euo pipefail
if ! echo "$TITLE" | grep -qE '^(feat|fix|docs|chore|refactor|test|ci|build|perf|style|revert)(\([a-z0-9._/ -]+\))?!?: .+'; then
echo "::error::PR title '$TITLE' does not follow the conventional-commit format, e.g. 'fix(wallet): reject stale BitBox pairing state'"
exit 1
fi
- name: Size cap (1500 changed lines, label size:override to bypass)
env:
ADDITIONS: ${{ github.event.pull_request.additions }}
DELETIONS: ${{ github.event.pull_request.deletions }}
HAS_OVERRIDE: ${{ contains(github.event.pull_request.labels.*.name, 'size:override') }}
run: |
set -euo pipefail
TOTAL=$((ADDITIONS + DELETIONS))
echo "changed lines: $TOTAL"
if [ "$TOTAL" -gt 1500 ] && [ "$HAS_OVERRIDE" != "true" ]; then
echo "::error::PR changes $TOTAL lines (cap 1500). Split it, or add the 'size:override' label with a justification in the description (goldens/codegen/lockfile churn qualifies)."
exit 1
fi
Loading