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
7 changes: 7 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
* @paulkakell
/assets/crypto*.js @paulkakell
/assets/passphrase.js @paulkakell
/.github/workflows/ @paulkakell
/SECURITY.md @paulkakell
/docs/FORMAT.md @paulkakell
/docs/THREAT_MODEL.md @paulkakell
20 changes: 20 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
version: 2
updates:
- package-ecosystem: npm
directory: /
schedule:
interval: weekly
day: monday
time: "09:00"
timezone: America/Denver
open-pull-requests-limit: 5
versioning-strategy: increase-if-necessary

- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
day: monday
time: "09:30"
timezone: America/Denver
open-pull-requests-limit: 5
47 changes: 47 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Security validation

on:
push:
branches: [dev]
pull_request:
branches: [main, dev]
workflow_dispatch:

permissions:
contents: read

concurrency:
group: security-validation-${{ github.ref }}
cancel-in-progress: true

jobs:
validate:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Check out source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Configure Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 22.16.0

- name: Install locked development tools
run: npm ci --ignore-scripts

- name: Audit development dependency
run: npm audit --audit-level=high

- name: Run complete validation suite
run: npm run validate

- name: Upload validated static artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: blindcrypt-${{ github.sha }}
path: dist/
if-no-files-found: error
retention-days: 14
41 changes: 41 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: CodeQL

on:
push:
branches: [main, dev]
pull_request:
branches: [main, dev]
schedule:
- cron: "17 9 * * 1"
workflow_dispatch:

permissions:
contents: read
security-events: write
packages: read
actions: read

concurrency:
group: codeql-${{ github.ref }}
cancel-in-progress: true

jobs:
analyze:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Check out source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Initialize CodeQL
uses: github/codeql-action/init@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7
with:
languages: javascript-typescript
queries: security-extended

- name: Analyze source
uses: github/codeql-action/analyze@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7
with:
category: /language:javascript-typescript
60 changes: 60 additions & 0 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
name: Deploy validated Pages artifact

on:
push:
branches: [main]
workflow_dispatch:

concurrency:
group: pages
cancel-in-progress: false

jobs:
build:
permissions:
contents: read
pages: write
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Check out source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Configure Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 22.16.0

- name: Configure Pages
uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0

- name: Install locked development tools
run: npm ci --ignore-scripts

- name: Audit development dependency
run: npm audit --audit-level=high

- name: Validate and build
run: npm run validate

- name: Upload Pages artifact
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
with:
path: dist

deploy:
needs: build
permissions:
pages: write
id-token: write
runs-on: ubuntu-latest
timeout-minutes: 10
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy Pages artifact
id: deployment
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules/
dist/
coverage/
*.log
.DS_Store
7 changes: 7 additions & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
audit=true
engine-strict=true
fund=false
ignore-scripts=true
package-lock=true
save-exact=true
update-notifier=false
75 changes: 75 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Changelog

BlindCrypt versions use `xx.xx.xx` as `<Release>.<Feature Update>.<Bug Fix>`.

## [01.01.01] - 2026-08-13

Status: development branch candidate. Release tag reserved: `v01.01.01` after merge and successful protected validation.

References: `GHAS-PR-1`, CodeQL alert 1, PR #1.

Baseline: `b57c01dd515011273832064f0645842655196be7`.

### Security fix

- Replaced the validation HTTP server's `stat()` followed by `readFile()` sequence, which CodeQL identified as a potential filesystem check/use race.
- Replaced request-derived filesystem resolution with an exact route allowlist. Request path data is now used only as a map key and never becomes a filesystem path.
- Added explicit rejection for unlisted paths, traversal-shaped requests, and non-GET methods.

### Tests and controls

- Added regression tests that prohibit reintroduction of `stat()` checks, dynamic path decoding, and request-derived file paths in the smoke server.
- Made the public-header tamper fixture choose a guaranteed-different writer value so release version changes cannot turn the security mutation into a no-op.
- Retained the runtime smoke checks for every expected build asset and added negative requests for traversal and unlisted paths.
- Updated the application version, changelog, SBOM, security policy, release notes, validation record, release checklist, README, and commit notes.

### Classification and compatibility

- Change type: security bug fix.
- Breaking: no.
- Encryption format: unchanged at v3.
- Reader compatibility: unchanged for v1, v2, and v3.
- Runtime dependencies: unchanged at zero.
- Database, backend, environment, and configuration migration: not applicable.

## [01.01.00] - 2026-08-13

Status: superseded before release by `01.01.01`.

Reference: `SEC-AUDIT-2026-08-13`.

Baseline: `4ed8c157c6015340b363848c12527d9499fb8d69`.

### Security fixes

- Added authenticated format v3. The exact public header frame, record type, record index, and plaintext record length are bound to every AES-GCM record through additional authenticated data.
- Added a fixed-size encrypted metadata record for filename, media type, and writer version.
- Added exact container-length verification, canonical public-header parsing, record-geometry checks, and rejection of truncation or trailing data.
- Added strict upper and lower bounds for KDF iterations, public-header length, salt and IV lengths, plaintext size, record count, metadata size, and passphrase length.
- Added a 64 MiB plaintext ceiling and slice-based Blob processing to reduce memory amplification.
- Added safe filename and MIME normalization. Legacy output uses a neutral filename and media type.
- Replaced misleading custom-passphrase entropy estimates. Generated word-list phrases retain transparent word-count estimates; custom passphrases receive no entropy claim.
- Removed the four-word generator option. The minimum generated phrase is six words. Repetitive custom values are rejected.
- Added NFC normalization for v3 while preserving exact passphrase behavior for legacy v1 and v2.
- Added a restrictive Content Security Policy, a no-referrer policy, local-only executable resources, and checks that prohibit network APIs, dynamic HTML sinks, persistent storage, and console logging.

### Additive changes

- Added read compatibility for v1, v2, and v3 through a single bounded parser.
- Added unit, integration, regression, tamper, normalization, and performance tests.
- Added strict JavaScript type checking, custom linting, local SAST, configuration validation, reproducible static builds, an HTTP artifact smoke test, SHA-256 manifests, an SPDX SBOM, dependency auditing, and CodeQL.
- Classified the bundled 2,048-word list as separately validated static data so security lint does not mistake dictionary words such as `fetch` for executable network APIs.
- Added pinned GitHub Actions workflows for validation, scanning, artifact retention, and Pages deployment.
- Added version, format, architecture, API, threat-model, validation, release, rollback, repository-settings, commit-note, and security-policy documentation.

### Removed

- Removed the unused placeholder `assets/wordlist_2048.js` file.
- Removed new-file format v2 output. Version 2 remains readable.
- Removed unverified custom entropy labels and the insecure four-word generation option.

### Compatibility

- Additive reader compatibility: version `01.01.00` reads v1, v2, and v3.
- Breaking producer change: files created by `01.01.00` use v3 and cannot be opened by the unversioned baseline.
- No backend API, database schema, environment-variable, or server configuration migration exists.
21 changes: 21 additions & 0 deletions COMMIT_NOTES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Commit notes for 01.01.01

```text
security: fix smoke-server filesystem race for 01.01.01

Release: 01.01.01
Tag after protected merge: v01.01.01
Refs: GHAS-PR-1, CodeQL alert 1, PR #1
Baseline: b57c01dd515011273832064f0645842655196be7

- replace stat-then-read validation with a fixed route allowlist
- prevent request paths from becoming filesystem paths
- reject traversal-shaped, unlisted, and non-GET requests
- add regression coverage for the CodeQL finding
- make authenticated-header tamper coverage deterministic across version increments
- update version, changelog, SBOM, security policy, release notes, validation, and rollback evidence

Change type: non-breaking security bug fix
Compatibility: format v3 writer and v1/v2/v3 reader behavior unchanged
Rollback: revert the 01.01.01 commits to the validated 01.01.00 candidate; retain format v3 support
```
Loading