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
58 changes: 58 additions & 0 deletions docs/reference/codeql-review-2026-09-23.md

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions docs/reference/package-api-migrations.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions governance/package-release-notes.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"publishedVersion": "2.2.3",
"releaseType": "patch",
"summary": "Requires the SDK release that binds authenticated BRC-104 request identity to the nonce-selected session; retains application certificate approval against that exact session; canonicalizes parsed request bodies; rejects partial, duplicate, malformed, and unsupported signed input; bounds handshake and response work; contains optional diagnostics; preserves Express sendFile controls; and signs responses emitted through standard Express and Node response methods.",
"migration": "No valid BRC-103/104 wire bytes are changed. Upgrade to @bsv/auth-express-middleware 2.2.5 with @bsv/sdk 2.7.1 or later. When onCertificatesReceived is configured, the callback must call its approval function before returning; replicated deployments must inject a shared CertificateApprovalStore as well as shared session state. Parsed URL-encoded objects are limited to flat string fields, and unsupported nonempty parsed bodies now fail closed. BRC-104 v0.1 does not sign Host/authority, cookies, forwarding metadata, arbitrary standard request headers, or arbitrary standard response headers; pin authority at a trusted edge and do not authorize from omitted metadata."
"migration": "No valid BRC-103/104 wire bytes are changed. Upgrade to @bsv/auth-express-middleware 2.2.5 with the coordinated @bsv/sdk 2.8.0 release or later. When onCertificatesReceived is configured, the callback must call its approval function before returning; replicated deployments must inject a shared CertificateApprovalStore as well as shared session state. Parsed URL-encoded objects are limited to flat string fields, and unsupported nonempty parsed bodies now fail closed. BRC-104 v0.1 does not sign Host/authority, cookies, forwarding metadata, arbitrary standard request headers, or arbitrary standard response headers; pin authority at a trusted edge and do not authorize from omitted metadata."
},
{
"name": "@bsv/authsocket",
Expand Down Expand Up @@ -140,7 +140,7 @@
"name": "@bsv/overlay-express",
"publishedVersion": "2.6.1",
"releaseType": "minor",
"summary": "Adds direct constructor configuration and hardens public overlay services with DNS-pinned public-HTTPS outbound requests, no redirects, finite deadlines and body limits, canonical reorganization checks, bounded health, search, and monitor routes, sanitized no-store administration pages, and token-gated ARC callbacks.",
"summary": "Adds direct constructor configuration and hardens public overlay services with DNS-pinned public-HTTPS outbound requests, no redirects, finite deadlines and body limits, canonical reorganization checks, bounded health, search, and monitor routes, sanitized no-store administration pages, and token-gated ARC callbacks. Serializes GASP request failures into one escaped field through the configured logger, including errors containing line separators or throwing serialization hooks.",
"migration": "Set an ARC callback token of at least 32 bytes before enabling the ARC ingestion route and update the sender to present it. Private-network or plaintext outbound destinations require the explicit allowPrivateHosts development policy; production endpoints must use public HTTPS. Detailed health data is opt-in. Existing canonical public overlay requests and default credential-free CORS remain supported, but operators must roll out callback credentials and egress policy together across every replica."
},
{
Expand Down
14 changes: 14 additions & 0 deletions governance/repository-health/exceptions.json
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,20 @@
"created": "2026-07-28",
"reviewBy": "2026-10-26",
"removeWhen": "Remove when the analyzer exempts exhaustive protocol dispatch or a separately reviewed generated opcode table improves auditability and passes every script vector, differential, mutation, fuzz, transaction, browser, and packed-consumer check."
},
{
"id": "codeql-reviewed-validation-and-test-fixtures-20260923",
"category": "quality-rule",
"target": "Existing CodeQL alerts #242, #250-258 and #261-278, individually reviewed on main 476b1351baf366cee39f39f610975f7fb1a83e4a",
"owner": "ts-stack-maintainers",
"reason": "The exact reported paths are existing primitive validation/authentication guards, contained metadata writes, exclusive file creation, exact array membership, or isolated test assertions. No security bypass is established. Rewriting safe behavior or removing meaningful tests solely to alter heuristic matches would weaken compatibility or assurance; no production control is waived. Detailed per-alert evidence and trust assumptions are retained for review.",
"evidence": [
"docs/reference/codeql-review-2026-09-23.md",
"https://github.com/bsv-blockchain/ts-stack/actions/runs/35813974981"
],
"created": "2026-09-23",
"reviewBy": "2026-10-23",
"removeWhen": "Remove when improved CodeQL modeling no longer reports these paths. Reopen affected alerts immediately if their documented guards, callers, filesystem trust assumptions or test-only roles change; any genuine unsafe path requires remediation before release."
}
]
}
2 changes: 2 additions & 0 deletions packages/overlays/overlay-express/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ All notable changes to this project will be documented in this file. The format

### 2.7.2 candidate — outbound and deployment hardening

- Serialize GASP route errors through the configured logger as one escaped field; HTTP error responses, public APIs and wire behavior are unchanged.

- Updates the packed workspace dependency candidate for the additive overlay persistence contract. Runtime behavior and defaults are unchanged; no consumer migration is required.
- Validated BASM heights, hashes and txid sets while retaining numeric-string
heights, public CORS, topic-free raw transactions, configured limits and sync
Expand Down
4 changes: 4 additions & 0 deletions packages/overlays/overlay-express/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ hash. Existing Chaintracks/provider adapters remain header-only; they do not
claim this stronger position evidence. See the core engine's
[BASM validation and recovery limits](../overlay/README.md#basm-peer-validation-and-current-recovery-limits).

GASP route failures use the configured logger and serialize thrown values into a
single escaped field, preserving diagnostic context without allowing request
content to forge additional log records. HTTP error responses remain unchanged.

## Example Usage

Here's a quick example:
Expand Down
8 changes: 6 additions & 2 deletions packages/overlays/overlay-express/src/OverlayExpress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2820,7 +2820,9 @@ export default class OverlayExpress {
const response = await engine.provideForeignSyncResponse(req.body, topic)
return res.status(200).json(response)
} catch (error) {
console.error(chalk.red('Error in /requestSyncResponse:'), error)
this.logger.error(
chalk.red(`Error in /requestSyncResponse: error=${serializeErrorForLog(error)}`)
)
return res.status(400).json({
status: 'error',
message: publicErrorMessage(error)
Expand All @@ -2845,7 +2847,9 @@ export default class OverlayExpress {
const response = await engine.provideForeignGASPNode(graphID, txid, outputIndex, header)
return res.status(200).json(response)
} catch (error) {
console.error(chalk.red('Error in /requestForeignGASPNode:'), error)
this.logger.error(
chalk.red(`Error in /requestForeignGASPNode: error=${serializeErrorForLog(error)}`)
)
return res.status(400).json({
status: 'error',
message: publicErrorMessage(error)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2146,6 +2146,49 @@ describe('OverlayExpress', () => {
})
})

it.each([
['/requestSyncResponse', 'provideForeignSyncResponse'],
['/requestForeignGASPNode', 'provideForeignGASPNode']
])('serializes untrusted %s errors through the configured logger', async (route, method) => {
const safeLog = jest.requireActual<typeof import('@bsv/overlay')>('@bsv/overlay')
jest.mocked(serializeErrorForLog).mockImplementation(safeLog.serializeErrorForLog)
const logger = { ...console, log: jest.fn(), warn: jest.fn(), error: jest.fn() }
instance.configureLogger(logger)
const consoleError = jest.spyOn(console, 'error').mockImplementation(() => {})
try {
const { postSpy } = await startAndCaptureRoutes()
for (const thrown of [
new Error('untrusted\r\nforged\u0085record\u2028separator\u2029'),
{
toJSON: () => {
throw new Error('unserializable request error')
}
}
]) {
logger.error.mockClear()
mockEngine[method].mockRejectedValueOnce(thrown)
const response = await invokeCapturedRoute(postSpy, route, {
headers: { 'x-bsv-topic': 'tm_test' },
body: { graphID: 'graph', txid: '01', outputIndex: 0 }
})
expect(response.status).toHaveBeenCalledWith(400)
expect(response.json).toHaveBeenCalledWith({
status: 'error',
message: 'Request could not be processed'
})
expect(logger.error).toHaveBeenCalledTimes(1)
const args = logger.error.mock.calls[0]
expect(args).toHaveLength(1)
expect(args[0]).toContain(`Error in ${route}: error=`)
expect(args[0]).not.toMatch(/[\r\n\u0085\u2028\u2029]/)
expect(args[0]).toContain(safeLog.serializeErrorForLog(thrown))
expect(consoleError).not.toHaveBeenCalled()
}
} finally {
consoleError.mockRestore()
}
})

it('executes public discovery, GASP, and bounded BASM routes', async () => {
const consoleError = jest.spyOn(console, 'error').mockImplementation(() => {})
const { getSpy, postSpy } = await startAndCaptureRoutes()
Expand Down
Loading