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
2 changes: 2 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ Use Node.js 22. Start from a clean checkout and run `npm ci --ignore-scripts`, `
Keep tests self-contained and use generated, disposable identities. Do not require production infrastructure, cloud credentials, or signing keys in a pull request. Never commit `.env` files, private keys, wallet seeds, database exports, production logs, or deployment access. Add tests for changed behavior, especially authorization and signing paths. Document any new local configuration in `.env.example` using names and safe placeholders only.

Open a focused pull request describing the behavior, risks, and verification. Keep dependency changes in the lockfile. If a security issue or suspected credential exposure is involved, follow [SECURITY.md](SECURITY.md) before opening a public issue.

Runtime dependency remediation and the preserved mobile error contract are recorded in [docs/SECURITY_REVIEW.md](docs/SECURITY_REVIEW.md). Review those notes alongside audit output; a passing local suite does not verify hosted provider delivery.
21 changes: 21 additions & 0 deletions docs/SECURITY_REVIEW.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Source security and dependency review

Reviewed 7 October 2026 with Node 22 and the committed lockfile. This document records source/contributor checks; it is not approval to deploy a hosted backend.

## Runtime dependencies

The lockfile pins patched `proxy-addr` 2.0.8 and `pbkdf2` 3.1.7. BIP39 published vectors and a long-passphrase fixture retain output compatibility. Moment 2.31.0 fixes [GHSA-4p3w-j4w9-5jqw](https://github.com/advisories/GHSA-4p3w-j4w9-5jqw); duration-format fixtures cover the email calls used here.

An explicit override resolves the four transitive UUID copies to CommonJS-compatible `uuid` 11.1.1, the patched version for [GHSA-w5hq-g745-h8pq](https://github.com/advisories/GHSA-w5hq-g745-h8pq). The callers in AWS SDK, google-gax, node-cron and teeny-request use `v4()` without a caller-provided buffer. Compatibility tests exercise AWS/Google UUID helpers, cron registration/manual execution and the Google HTTP client's multipart boundary against a loopback server. This avoids changing cloud-client or cron lifecycle APIs merely to update the transitive package. Keep the override visible until those upstream chains support a patched version themselves.

`npm audit --omit=dev --package-lock-only` reports no critical, high or moderate runtime findings after these changes. One low AWS SDK v2 region-validation advisory remains: [GHSA-j965-2qgj-vjmq](https://github.com/advisories/GHSA-j965-2qgj-vjmq). The SES client region here comes from deployment configuration (`SES_REGION`), not request data. Restrict that configuration to the deployment's valid AWS region. Do not follow the audit suggestion to downgrade AWS SDK to v1. Plan the supported AWS SDK v3 migration separately, with a staging SES send/response test and runtime identity review. Unit and local contributor checks do not contact SES or Google Secret Manager.

## HTTP errors and logging

The mobile client consumes the existing `{ err: message }` response from Signing Server routes. Changing all failures to generic text or a new code schema would change verification/signing error behavior. Preserve that response contract in the logging-only change. A future public-error contract should explicitly allow stable expected route errors and sanitize provider/database exceptions, with mobile acceptance fixtures per route before any hosted rollout.

Never put request bodies, seeds, keys, OTPs, access configuration, provider exception contents or real user logs into an issue or PR. Source/history secret scans and synthetic error-canary tests belong in review. Source scans cannot establish what a separate hosted system logged historically; any investigation or credential change must follow the private security process and its production dependencies.

## Contributor verification

Use a fresh public clone, Node 22, `npm ci --ignore-scripts`, `npm run compile`, `npm test`, the runtime audit above, and both current-source and history scans documented in CONTRIBUTING. Tests use generated disposable identities and deny live Secret Manager access. Retain the MIT/ISC notices and SECURITY reporting channel when distributing the source.
121 changes: 47 additions & 74 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"fs": "0.0.1-security",
"http": "0.0.0",
"idx": "3.0.1",
"moment": "2.30.1",
"moment": "2.31.0",
"mongoose": "6.13.11",
"morgan": "1.12.1",
"node-cron": "3.0.2",
Expand All @@ -53,5 +53,10 @@
"tslint": "5.12.1",
"typescript": "5.0.4",
"@types/express": "4.16.0"
},
"overrides": {
"pbkdf2": "3.1.7",
"proxy-addr": "2.0.8",
"uuid": "11.1.1"
}
}
26 changes: 26 additions & 0 deletions tests/unit/bip39Compatibility.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import * as crypto from "crypto";
import * as bip39 from "bip39";

// Published BIP39 English test vector; this mnemonic never held funds.
const mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";

describe("BIP39 seed compatibility after PBKDF2 patch", () => {
it("matches the published passphrase vector", () => {
expect(bip39.mnemonicToSeedSync(mnemonic, "TREZOR").toString("hex")).toBe(
"c55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e534955" +
"31f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04"
);
});

it("matches Node crypto with a long synthetic passphrase", () => {
const passphrase = "keeper-disposable-test-".repeat(12);
const expected = crypto.pbkdf2Sync(
mnemonic.normalize("NFKD"),
`mnemonic${passphrase}`.normalize("NFKD"),
2048,
64,
"sha512"
);
expect(bip39.mnemonicToSeedSync(mnemonic, passphrase)).toEqual(expected);
});
});
Loading
Loading