Skip to content

tx-history model assertion#448

Open
ronaldsg20 wants to merge 10 commits intomainfrom
sec/3.3.1
Open

tx-history model assertion#448
ronaldsg20 wants to merge 10 commits intomainfrom
sec/3.3.1

Conversation

@ronaldsg20
Copy link
Member

No description provided.

Adding model validation and tests for tx-history
@github-actions
Copy link

github-actions bot commented Jan 30, 2026

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

OpenSSF Scorecard

PackageVersionScoreDetails
npm/nock 13.5.6 🟢 4.7
Details
CheckScoreReason
Code-Review🟢 6Found 12/18 approved changesets -- score normalized to 6
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Maintained🟢 78 commit(s) and 1 issue activity found in the last 90 days -- score normalized to 7
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
Binary-Artifacts🟢 10no binaries found in the repo
Pinned-Dependencies🟢 3dependency not pinned by hash detected -- score normalized to 3
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Fuzzing⚠️ 0project is not fuzzed
License🟢 10license file detected
Branch-Protection⚠️ -1internal error: error during branchesHandler.setup: internal error: some github tokens can't read classic branch protection rules: https://github.com/ossf/scorecard-action/blob/main/docs/authentication/fine-grained-auth-token.md
Signed-Releases⚠️ -1no releases found
Security-Policy⚠️ 0security policy file not detected
Packaging🟢 10packaging workflow detected
SAST⚠️ 0SAST tool is not run on all commits -- score normalized to 0
Vulnerabilities⚠️ 017 existing vulnerabilities detected

Scanned Files

  • package-lock.json

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds comprehensive validation and schema assertions to the transaction history API models and controllers. It extends support for additional tokens and networks, adds new optional fields for liquidity provider tracking, implements strict input validation patterns, and includes extensive acceptance tests to verify the validation behavior.

Changes:

  • Extended TOKENS enum to include ETH, USDT, USDC, DAI, WBTC, BNB, and Fiat
  • Extended NETWORKS enum to include Ethereum, BNB Smart Chain, and Lightning Network
  • Added liquidityProviderName and liquidityProviderId optional fields to TxHistory model with validation
  • Added comprehensive input validation patterns to controller endpoints (address, page, txHash)
  • Added txHash validation pattern to RegisterPayload model
  • Added 533 lines of acceptance tests covering positive and negative validation scenarios
  • Modified test script to run both unit and acceptance tests together
  • Downgraded nock package from v14 to v13
  • Fixed import ordering in fireblocks acceptance test
  • Updated input sanitization test to verify SQL injection prevention in txHash field

Reviewed changes

Copilot reviewed 7 out of 8 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
src/models/tx-history.model.ts Added new tokens/networks to enums, added liquidityProviderName and liquidityProviderId fields with validation
src/models/register-payload.model.ts Added regex validation pattern for txHash field to prevent injection attacks
src/controllers/tx-history.controller.ts Added parameter validation schemas for address, page, and txHash with regex patterns and error responses
src/tests/acceptance/tx-history.acceptance.ts New comprehensive acceptance test suite with 533 lines testing all validation scenarios
src/tests/acceptance/input-sanitization.acceptance.ts Updated SQL injection test to expect rejection (422) instead of acceptance, removed skipped hex data test
src/tests/acceptance/fireblocks.controller.acceptance.ts Reordered imports to follow consistent pattern
package.json Modified test script to run both unit and acceptance tests; downgraded nock from v14 to v13
package-lock.json Updated dependency tree reflecting nock downgrade and removed related dependencies

Copilot AI review requested due to automatic review settings February 13, 2026 17:46
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 9 changed files in this pull request and generated 8 comments.

Copilot AI review requested due to automatic review settings February 13, 2026 19:52
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 9 changed files in this pull request and generated 4 comments.

import {SearchableModel} from './rsk/searchable-model';

export const SOURCES = ['FLYOVER', 'SWAP', 'POWPEG', 'UNION', 'LIFI', 'SYMBIOSIS'] as const;
export const SOURCES = ['FLYOVER', 'SWAP', 'POWPEG', 'UNION', 'LIFI', 'SYMBIOSIS', 'teks-staging'] as const;
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The value 'teks-staging' added to SOURCES uses lowercase with a hyphen, which is inconsistent with all other values in the array that use uppercase letters (e.g., 'FLYOVER', 'SWAP', 'POWPEG', 'UNION', 'LIFI', 'SYMBIOSIS'). This inconsistency could cause confusion for API consumers and violates the established naming convention for this enum. Consider renaming it to 'TEKS_STAGING' or 'TEKS-STAGING' to match the existing pattern.

Suggested change
export const SOURCES = ['FLYOVER', 'SWAP', 'POWPEG', 'UNION', 'LIFI', 'SYMBIOSIS', 'teks-staging'] as const;
export const SOURCES = ['FLYOVER', 'SWAP', 'POWPEG', 'UNION', 'LIFI', 'SYMBIOSIS', 'TEKS-STAGING'] as const;

Copilot uses AI. Check for mistakes.
Comment on lines +96 to +97


Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are two blank lines (96-97) in the middle of the method implementation. This appears to be unintentional and reduces code readability. Remove the extra blank line to maintain consistent code formatting.

Suggested change

Copilot uses AI. Check for mistakes.
Comment on lines +94 to +97
}



Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method verifyTransactionExistsOnBlockchain returns true for networks other than 'bitcoin' and 'rootstock', but the NETWORKS constant now includes 'Ethereum', 'BNB Smart Chain', and 'Lightning Network'. This means transactions from these networks will be accepted without any blockchain verification. This creates a security risk where invalid transaction hashes could be stored for these networks. Either implement verification for the newly-added networks or explicitly return false for unsupported networks to prevent storing unverified transactions.

Suggested change
}
} else {
this.logger.warn(
`[verifyTransactionExists] Unsupported network for verification: ${txHistory.fromNetworkName}`,
);
return false;
}

Copilot uses AI. Check for mistakes.
required: true,
schema: {
type: 'string',
pattern: '^[a-fA-F0-9]{64}$|^0x[a-fA-F0-9]{64}$',
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pattern ^[a-fA-F0-9]{64}$|^0x[a-fA-F0-9]{64}$ will always match with the first alternative ^[a-fA-F0-9]{64}$ for strings without the '0x' prefix because the regex alternation is evaluated left-to-right. However, due to the anchor ^ at the start of the second alternative, a string like '0x1234...' will fail the first alternative and then be tested by the second. The pattern works correctly but could be more efficiently written as ^(0x)?[a-fA-F0-9]{64}$. Consider refactoring for clarity and consistency with the pattern used in tx-history.model.ts line 29: ^(0x[a-fA-F0-9]{64}|[a-fA-F0-9]{64})$

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings February 18, 2026 12:15
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 9 changed files in this pull request and generated 10 comments.

txHistory.fromAmount = rskTransaction.value?.toString() ?? '';
txHistory.userAddress = rskTransaction.from?.toString() ?? '';
} else {
return null as unknown as TxHistory;
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using null as unknown as TxHistory to return a falsy value is an anti-pattern. The method should have a clearer return type. Consider changing the method signature to return Promise<boolean> and return true when the transaction exists and false when it doesn't, instead of using null casts. This would make the code more readable and type-safe.

Copilot uses AI. Check for mistakes.
Comment on lines +45 to +57
it('should store a valid transaction and return 200', async () => {
const requestBody = getValidRequestBody();

storeTransactionStub = sinon.stub(TxHistoryService.prototype, 'storeTransaction').resolves(true);

const res = await client
.post('/tx-history')
.send(requestBody)
.expect(200);

expect(res.body).to.be.empty();
sinon.assert.calledOnce(storeTransactionStub);
});
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The acceptance test stubs TxHistoryService.storeTransaction but does not stub BitcoinService.getTx or RskNodeService.getTransaction. Since the storeTransaction endpoint now calls verifyTransactionExistsOnBlockchain (which calls these services) before storing, the acceptance tests will likely fail or make real network calls. You should stub these services in the acceptance tests to prevent network calls and ensure tests are isolated.

Copilot uses AI. Check for mistakes.
return !!transaction;
}

private async verifyTransactionExistsOnBlockchain(txHistory: TxHistory): Promise<TxHistory> {
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method verifyTransactionExistsOnBlockchain returns TxHistory (or null cast as TxHistory), but it's being used as a boolean in line 57 with if (!transactionExists). When the method returns null (cast as TxHistory), this will be falsy and work as expected. However, when it returns a TxHistory object, the truthiness check will pass. The return type should be changed to Promise<boolean> or Promise<TxHistory | null>, and the usage should be updated accordingly. Additionally, the method mutates the input txHistory parameter (lines 93-94, 97-98) and returns it, which is confusing since the caller doesn't use the returned value except for the truthiness check.

Copilot uses AI. Check for mistakes.
this.logger.warn(
`[verifyTransactionExists] Transaction not found for ${txHistory.fromNetworkName}: ${txHistory.txHash} ${errorMessage}`,
);
return null as unknown as TxHistory;
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using null as unknown as TxHistory to return a falsy value is an anti-pattern. The method should have a clearer return type. Consider changing the method signature to return Promise<boolean> and return true when the transaction exists and false when it doesn't, instead of using null casts. This would make the code more readable and type-safe.

Copilot uses AI. Check for mistakes.
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : String(error);
this.logger.warn(
`[verifyTransactionExists] Transaction not found for ${txHistory.fromNetworkName}: ${txHistory.txHash} ${errorMessage}`,
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The log message references '[verifyTransactionExists]' but the method name is 'verifyTransactionExistsOnBlockchain'. Update the log message to use the correct method name for consistency and easier debugging.

Suggested change
`[verifyTransactionExists] Transaction not found for ${txHistory.fromNetworkName}: ${txHistory.txHash} ${errorMessage}`,
`[verifyTransactionExistsOnBlockchain] Transaction not found for ${txHistory.fromNetworkName}: ${txHistory.txHash} ${errorMessage}`,

Copilot uses AI. Check for mistakes.
import {SearchableModel} from './rsk/searchable-model';

export const SOURCES = ['FLYOVER', 'SWAP', 'POWPEG', 'UNION', 'LIFI', 'SYMBIOSIS'] as const;
export const SOURCES = ['FLYOVER', 'SWAP', 'POWPEG', 'UNION', 'LIFI', 'SYMBIOSIS', 'teks-staging'] as const;
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The value 'teks-staging' in SOURCES uses kebab-case while all other values use SCREAMING_SNAKE_CASE. This is inconsistent with the established naming pattern. Consider renaming to 'TEKS_STAGING' to maintain consistency.

Suggested change
export const SOURCES = ['FLYOVER', 'SWAP', 'POWPEG', 'UNION', 'LIFI', 'SYMBIOSIS', 'teks-staging'] as const;
export const SOURCES = ['FLYOVER', 'SWAP', 'POWPEG', 'UNION', 'LIFI', 'SYMBIOSIS', 'TEKS_STAGING'] as const;

Copilot uses AI. Check for mistakes.
Comment on lines +93 to +94
txHistory.fromAmount = btcTransaction.amount.toString();
txHistory.userAddress = btcTransaction.address;
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code attempts to access btcTransaction.amount and btcTransaction.address properties (lines 93-94), but the BitcoinTx model does not define these properties. The BitcoinTx model only has properties like txid, vin, vout, valueOut, valueIn, etc. This will result in undefined values being assigned to txHistory.fromAmount and txHistory.userAddress, causing the verification to fail silently. You need to extract the amount and address from the available properties (e.g., from vout for amount and vin for address) or update the BitcoinService.getTx to return the required properties.

Suggested change
txHistory.fromAmount = btcTransaction.amount.toString();
txHistory.userAddress = btcTransaction.address;
const amount =
(btcTransaction as any).valueOut ??
((btcTransaction as any).vout
? (btcTransaction as any).vout.reduce(
(sum: number, output: any) => sum + (output?.value ?? 0),
0,
)
: undefined);
txHistory.fromAmount = amount != null ? amount.toString() : '';
const inputAddress =
(btcTransaction as any).vin?.[0]?.addr ??
(btcTransaction as any).vin?.[0]?.addresses?.[0] ??
(btcTransaction as any).vout?.[0]?.scriptPubKey?.addresses?.[0] ??
'';
txHistory.userAddress = inputAddress;

Copilot uses AI. Check for mistakes.
@@ -1,3 +1,4 @@
/* eslint-disable no-param-reassign */
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The eslint-disable directive for no-param-reassign is applied to the entire file, but parameter reassignment only occurs in the verifyTransactionExistsOnBlockchain method (lines 93-94, 97-98). Consider moving this directive to only disable the rule for the specific method or lines where it's needed, rather than the entire file.

Copilot uses AI. Check for mistakes.
Comment on lines 113 to +242
@@ -89,11 +162,25 @@ export class TxHistoryController {
},
},
},
'400': {
description: 'Invalid address or page parameter',
},
},
})
async getTransactionHistory(
@param.query.string('address', {required: true}) address: string,
@param.query.number('page') page: number = 1,
@param.query.string('address', {
required: true,
schema: {
type: 'string',
pattern: '^(0x[a-fA-F0-9]{40}|[13mn][a-km-zA-HJ-NP-Z1-9]{25,34}|2[a-km-zA-HJ-NP-Z1-9]{25,34}|(bc1q|tb1q)[0-9a-z]{38,59}|(bc1p|tb1p)[0-9a-z]{39,59})$',
},
}) address: string,
@param.query.number('page', {
schema: {
type: 'number',
minimum: 1,
},
}) page: number = 1,
): Promise<Response> {
try {
if (!address) {
@@ -118,6 +205,18 @@ export class TxHistoryController {
}

@get('/tx-history/{txHash}', {
parameters: [
{
name: 'txHash',
in: 'path',
required: true,
schema: {
type: 'string',
pattern: '^[a-fA-F0-9]{64}$|^0x[a-fA-F0-9]{64}$',
},
description: 'Must be a valid transaction hash',
},
],
responses: {
'200': {
description: 'Transaction details',
@@ -127,13 +226,21 @@ export class TxHistoryController {
},
},
},
'400': {
description: 'Invalid transaction hash format',
},
'404': {
description: 'Transaction not found',
},
},
})
async getTransactionByHash(
@param.path.string('txHash') txHash: string,
@param.path.string('txHash', {
schema: {
type: 'string',
pattern: '^[a-fA-F0-9]{64}$|^0x[a-fA-F0-9]{64}$',
},
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The address validation pattern is duplicated in three places: in the parameters array (lines 113-123), in the @param decorator (lines 171-177), and in the GET endpoint parameters. The txHash pattern is also duplicated (lines 208-218 and 238-242). Consider extracting these patterns to constants to improve maintainability and reduce duplication.

Copilot uses AI. Check for mistakes.
"only-coverage": "lb-nyc report --reporter=lcov",
"pretest": "npm run rebuild",
"test": "npm run unit-test",
"test": "npm run pretest && lb-nyc mocha --recursive 'dist/__tests__/**/*.unit.js' 'dist/__tests__/**/*.acceptance.js' --timeout 30000 --reporter spec",
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test script has been changed to run both unit and acceptance tests together instead of just unit tests. This changes the behavior of npm test to run a more comprehensive test suite. Ensure this change is intentional and that CI/CD pipelines are updated accordingly, as the test execution time will likely increase significantly.

Suggested change
"test": "npm run pretest && lb-nyc mocha --recursive 'dist/__tests__/**/*.unit.js' 'dist/__tests__/**/*.acceptance.js' --timeout 30000 --reporter spec",
"test": "npm run unit-test",

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings February 18, 2026 14:26
const requestBody = getValidRequestBody();

storeTransactionStub = sinon.stub(TxHistoryService.prototype, 'storeTransaction').resolves(true);
const getTransactionByHashStub = sinon.stub(TxHistoryService.prototype, 'getTransactionByHash').resolves(null);

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note test

Unused variable getTransactionByHashStub.

Copilot Autofix

AI about 1 month ago

In general, to fix an unused variable where the expression’s side effect is required, you should keep the expression but remove the variable binding. Here, we still want to stub TxHistoryService.prototype.getTransactionByHash so that the real implementation is not called, but we do not need to store the stub instance in getTransactionByHashStub.

Concretely, in src/__tests__/acceptance/tx-history.acceptance.ts, within the "should store a valid transaction and return 200" test, replace the line that declares and assigns getTransactionByHashStub with a plain sinon.stub(...) call that is not assigned to any variable. No imports or other lines need to change, and we do not need to touch storeTransactionStub or any other code.

Suggested changeset 1
src/__tests__/acceptance/tx-history.acceptance.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/__tests__/acceptance/tx-history.acceptance.ts b/src/__tests__/acceptance/tx-history.acceptance.ts
--- a/src/__tests__/acceptance/tx-history.acceptance.ts
+++ b/src/__tests__/acceptance/tx-history.acceptance.ts
@@ -72,7 +72,7 @@
       const requestBody = getValidRequestBody();
 
       storeTransactionStub = sinon.stub(TxHistoryService.prototype, 'storeTransaction').resolves(true);
-      const getTransactionByHashStub = sinon.stub(TxHistoryService.prototype, 'getTransactionByHash').resolves(null);
+      sinon.stub(TxHistoryService.prototype, 'getTransactionByHash').resolves(null);
 
       const res = await client
         .post('/tx-history')
EOF
@@ -72,7 +72,7 @@
const requestBody = getValidRequestBody();

storeTransactionStub = sinon.stub(TxHistoryService.prototype, 'storeTransaction').resolves(true);
const getTransactionByHashStub = sinon.stub(TxHistoryService.prototype, 'getTransactionByHash').resolves(null);
sinon.stub(TxHistoryService.prototype, 'getTransactionByHash').resolves(null);

const res = await client
.post('/tx-history')
Copilot is powered by AI and may make mistakes. Always verify output.
@sonarqubecloud
Copy link

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants