diff --git a/.github/workflows/build-platform-tfms.yml b/.github/workflows/build-platform-tfms.yml index bb10fbb..9121560 100644 --- a/.github/workflows/build-platform-tfms.yml +++ b/.github/workflows/build-platform-tfms.yml @@ -21,6 +21,9 @@ name: Build platform-specific TFMs on: workflow_dispatch: +permissions: + contents: read + jobs: ios: name: Build net10.0-ios @@ -29,10 +32,10 @@ jobs: runs-on: macos-14 steps: - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 - name: Setup .NET - uses: actions/setup-dotnet@v5 + uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5 - name: Install ios workload run: dotnet workload install ios @@ -47,10 +50,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 - name: Setup .NET - uses: actions/setup-dotnet@v5 + uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5 - name: Install android workload run: dotnet workload install android @@ -68,10 +71,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 - name: Setup .NET - uses: actions/setup-dotnet@v5 + uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5 - name: Install wasm-tools workload run: dotnet workload install wasm-tools diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4f2bfc2..42ccfbc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,9 @@ on: push: branches: [master] +permissions: + contents: read + # One CI run per ref at a time; a push flurry cancels superseded runs # instead of stacking them. (Once, 18 concurrently-hung runs exhausted the # account's ~20 concurrent-job quota and starved every other workflow.) @@ -51,7 +54,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 # global.json pins the SDK floor at 10.0.300 (with # `rollForward: latestFeature`, so any 10.0.3xx-or-newer SDK @@ -63,7 +66,7 @@ jobs: # global.json when no version is given, installing the requested # floor on the runner if it isn't already present. - name: Setup .NET - uses: actions/setup-dotnet@v5 + uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5 # GitHub-hosted runners exist for Windows, Linux, and macOS but NOT # for iOS, Android, or browser WebAssembly. Those TFMs are diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..92acc3a --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,59 @@ +name: CodeQL + +# Static analysis for the C# surface of this repo — runs on every push/PR to +# master plus a weekly schedule, so newly-added CodeQL query patterns get a +# chance to flag existing code, not just new diffs. +# +# Scoped to net10.0 on ubuntu-latest via CI_TFMS (the same mechanism +# ci.yml's ubuntu leg uses): CodeQL needs one successful build to extract +# from, and the platform TFMs (ios/android/macos/browser) need workloads +# and runners this leg doesn't have — build-platform-tfms.yml and +# release.yml already compile-check those separately. + +on: + push: + branches: [master] + pull_request: + branches: [master] + schedule: + - cron: '24 3 * * 1' # weekly, Monday 03:24 UTC + +concurrency: + group: codeql-${{ github.ref }} + cancel-in-progress: true + +jobs: + analyze: + name: Analyze (C#) + runs-on: ubuntu-latest + timeout-minutes: 20 + # Job-level permissions replace (not merge with) any workflow-level + # block, so both grants live here even though only one job exists — + # scoping them to the job that actually needs them, not the workflow. + permissions: + contents: read + security-events: write + env: + CI_TFMS: net10.0 + steps: + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false + + - name: Setup .NET + uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5 + + - name: Initialize CodeQL + uses: github/codeql-action/init@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4 + with: + languages: csharp + + - name: Restore + run: dotnet restore + + - name: Build + run: dotnet build TopSecret.ProtectedString.sln --configuration Release --no-restore + + - name: Perform CodeQL analysis + uses: github/codeql-action/analyze@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4 diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml index 24cd6dd..64e156a 100644 --- a/.github/workflows/dependabot-auto-merge.yml +++ b/.github/workflows/dependabot-auto-merge.yml @@ -13,7 +13,7 @@ jobs: steps: - name: Fetch Dependabot metadata id: meta - uses: dependabot/fetch-metadata@v3 + uses: dependabot/fetch-metadata@25dd0e34f4fe68f24cc83900b1fe3fe149efef98 # v3 with: github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 9e9b01b..baf750c 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -41,11 +41,11 @@ jobs: url: ${{ steps.deployment.outputs.page_url }} steps: - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 # Honours global.json (SDK floor 10.0.300, rollForward latestFeature). - name: Setup .NET - uses: actions/setup-dotnet@v5 + uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5 - name: Install wasm-tools workload run: dotnet workload install wasm-tools @@ -78,10 +78,10 @@ jobs: echo "bundle=$bundle" >> "$GITHUB_ENV" - name: Upload Pages artifact - uses: actions/upload-pages-artifact@v5 + uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5 with: path: ${{ env.bundle }} - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v5 + uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 81afd85..600d0d8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -47,7 +47,7 @@ jobs: tag: ${{ steps.rp.outputs.tag_name }} steps: - id: rp - uses: googleapis/release-please-action@v5 + uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5 with: config-file: release-please-config.json manifest-file: .release-please-manifest.json @@ -81,12 +81,12 @@ jobs: echo "version=${TAG#v}" >> "$GITHUB_OUTPUT" - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: ref: ${{ steps.tag.outputs.name }} - name: Setup .NET - uses: actions/setup-dotnet@v5 + uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5 # macos-14 is the only hosted runner where all five installs succeed — # required for the main library's six-TFM pack. diff --git a/README.md b/README.md index d7fc45d..2aff1ad 100644 --- a/README.md +++ b/README.md @@ -794,6 +794,8 @@ The fork's fix (v2.0.0) runs the first lane inline on the calling thread and dis The 32-byte process master key plus its protector live once per process, regardless of how many `ProtectedString`s you allocate. Memory locking has a per-process budget on libc targets — see [Memory-locking policy](#memory-locking-policy) for the budget number and the failure-behaviour knob. +Content length is checked against 32-bit overflow before encryption: a plaintext at or beyond `int.MaxValue / 2` UTF-16 chars (~1.07 billion, since each char becomes 2 ciphertext bytes) throws `OverflowException` instead of silently under-allocating. In practice you'll hit `Array.MaxLength` or run out of process memory well before that threshold. + ## Build & test ``` diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..daea2de --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,47 @@ +# Security Policy + +## Supported versions + +Only the latest published version of each package (currently `2.x`) receives +security fixes. There is no LTS branch — given the release cadence, upgrading +is expected to be the fix. + +## Reporting a vulnerability + +Please **do not open a public GitHub issue** for a suspected vulnerability — +in the memory-protection guarantees (secrets recoverable from a memory or +core dump, a key-wrapping tier not doing what it claims, a timing +side-channel in a comparison, a wipe that doesn't happen on some exit path) +or in the supply chain (a CI/release workflow that could be tricked into +running untrusted code or exfiltrating secrets). + +Instead, use GitHub's private vulnerability reporting for this repository: +[Report a vulnerability](https://github.com/Alpaq92/TopSecret.ProtectedString/security/advisories/new). + +Include, where you can: + +- The affected package(s) and version. +- A description of the weakness and, if possible, a minimal repro. +- The platform/TFM it applies to, if platform-specific — this library's + security guarantees differ by key-at-rest tier; see the README's + [Security model](README.md#security-model) section. + +## Response + +This is a solo-maintained project with no formal SLA. Reports are triaged +and acknowledged as soon as reasonably possible, and a confirmed +vulnerability is prioritized over new feature work. + +## Scope + +**In scope:** the packages in this repository (`TopSecret.ProtectedString`, +`TopSecret.ProtectedBlob`, the TPM/Configuration satellites, and the Roslyn +analyzer) and the GitHub Actions workflows that build, test, and release +them. + +**Out of scope:** the demo apps (`TopSecret.Demo`, `TopSecret.Demo.Wasm`) +except where a demo bug points at a real library issue, and anything already +documented as a known, deliberate limitation in the README's +["What this library does **not** do"](README.md#what-this-library-does-not-do-and-why) +section — those are accepted trade-offs, not vulnerabilities, unless you can +show the trade-off is worse in practice than documented. diff --git a/TopSecret.Demo.Wasm/wwwroot/index.html b/TopSecret.Demo.Wasm/wwwroot/index.html index ce48cd4..b14a465 100644 --- a/TopSecret.Demo.Wasm/wwwroot/index.html +++ b/TopSecret.Demo.Wasm/wwwroot/index.html @@ -97,9 +97,14 @@ unthemed by default — the browser renders it (track, thumb, and especially the corner where the vertical scrollbar meets the bottom edge) in its native light colours regardless of page - theme, showing up as a stray white patch in dark mode. */ + theme, showing up as a stray white patch in dark mode. + xterm.css also hardcodes background-color: #000 on the viewport + and (as of xterm 6) never re-syncs it to the theme, so the + leftover strip below the last full row of cells shows through + as a black bar in light mode. */ #terminal .xterm-viewport { scrollbar-color: var(--border) var(--bg); + background-color: var(--bg); } #terminal .xterm-viewport::-webkit-scrollbar { width: 10px; diff --git a/TopSecret.ProtectedString/ProtectedString.cs b/TopSecret.ProtectedString/ProtectedString.cs index 7ea9332..5c9366d 100644 --- a/TopSecret.ProtectedString/ProtectedString.cs +++ b/TopSecret.ProtectedString/ProtectedString.cs @@ -1294,7 +1294,7 @@ private void EncryptInternal(ReadOnlySpan plain) // change which key we encrypt under mid-operation. Debug.Assert(_instanceProtector is not null, "InitInstance must run before EncryptInternal"); - int byteLen = plain.Length * 2; + int byteLen = checked(plain.Length * 2); // Allocate (and lock) the new state up front. If anything in here // throws — e.g., the configured policy is Throw and a lock failed —