Files
thePR0M3TH3ANandClaude Fable 5 25df5140b3 Audit round 3: live-sub budgets, revocation-before-courtesy, pending-key catch, CI semgrep, input bounds
BL-25 (medium): live subscriptions get the same adversarial-relay
posture buffered queries got in round 2 -- a bounded already-seen id
cache (2048 ids, FIFO eviction) so replaying one retained valid event
costs one verification and ONE delivery ever, plus a fixed-window
verification budget (100 per 10s) that quarantines distinct-event spray
for the rest of the window. The audit's 2,000-replay probe now produces
one callback, bounded by test.

BL-26 (medium): teardown order inverted. retireNip46Client pre-signs
the courtesy logout event while the key exists, then close() runs
IMMEDIATELY -- rejecting every in-flight request and wiping keys -- and
only then is the pre-signed event delivered, asynchronously and bounded
(publishCourtesy, safe after close). The audit's probe -- a pending
get_public_key resolving after logout began -- is now impossible and
regression-tested: in-flight requests reject within the same tick of
close().

BL-27 (low): a nostrconnect listen that fails naturally (timeout,
unreachable relays) discards the pending ephemeral key in a guarded
catch -- ownership-transfer on success unchanged.

BL-28 (low): the hosted verify job installs the pinned Semgrep 1.171.0
before security:ci, which round 2 taught to require it; the job was red
at the previous HEAD and skipping its SBOM/checksum artifacts.

Informational: bunker URI input bounds -- 4096 chars total, 256-char
secret (rejected beyond), 512-char relay URLs (filtered beyond).

8 new/updated adversarial tests; 266 total, all green.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-04 14:31:25 -04:00

59 lines
2.0 KiB
YAML

name: Security and release integrity
on:
push:
branches: [main]
pull_request:
workflow_dispatch:
permissions:
contents: read
jobs:
verify:
runs-on: ubuntu-latest
timeout-minutes: 25
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
persist-credentials: false
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 22
cache: npm
- run: npm ci --ignore-scripts
- name: Install pinned Semgrep (security:ci runs the custom rules)
run: pipx install semgrep==1.171.0
- name: Run build, typecheck, tests, dependency audit, and static-analysis gate
run: npm run security:ci
- name: Generate checksums and CycloneDX SBOM
shell: bash
run: |
mkdir -p security-artifacts
npm sbom --sbom-format cyclonedx > security-artifacts/sbom.cdx.json
find packages/widget/dist packages/demo/dist -type f -print0 | sort -z | xargs -0 sha256sum > security-artifacts/SHA256SUMS
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: bitlogin-security-artifacts
path: security-artifacts/
if-no-files-found: error
static-analysis:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
persist-credentials: false
fetch-depth: 0
- name: Install pinned Semgrep
run: pipx install semgrep==1.171.0
- name: Scan repository rules without per-rule timeouts
run: semgrep scan --config auto --error --timeout 0 packages
- name: Enforce local randomness rules (entropy audit §13)
run: semgrep scan --config .semgrep.yml --error --timeout 0 packages
- name: Scan repository history for secrets
uses: gitleaks/gitleaks-action@ff98106e4c7b2bc287b24eaf42907196329070c7 # v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}