Skip to content

Commit

Permalink
feat: add verification for transaction notes
Browse files Browse the repository at this point in the history
  • Loading branch information
PooyaRaki committed Feb 24, 2025
1 parent 43fc2e9 commit d1623f2
Show file tree
Hide file tree
Showing 6 changed files with 182 additions and 3 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"redis": "^4.7.0",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1",
"sanitize-html": "^2.14.0",
"semver": "^7.6.3",
"typeorm": "^0.3.20",
"viem": "^2.22.8",
Expand All @@ -72,6 +73,7 @@
"@types/jsonwebtoken": "^9",
"@types/lodash": "^4.17.14",
"@types/node": "^22.13.1",
"@types/sanitize-html": "^2",
"@types/semver": "^7.5.8",
"@types/supertest": "^6.0.2",
"aws-sdk-client-mock": "^4.1.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,12 @@ describe('Propose transaction - Transactions Controller (Unit)', () => {
const contract = contractBuilder().build();
const transaction = (await multisigTransactionBuilder())
.with('safe', safeAddress)
.with(
'origin',
`{"url": "${faker.internet.url({
appendSlash: false,
})}", "name": "${faker.word.words()}", "note": "<script>document.write('<img src=s onerror=alert(Hello World)>')</script>"}`,
)
.build();
transaction.safeTxHash = getSafeTxHash({ safe, transaction, chainId });
const signature = await signer.sign({
Expand Down Expand Up @@ -203,6 +209,7 @@ describe('Propose transaction - Transactions Controller (Unit)', () => {
safeAppInfo: expect.any(Object),
safeAddress,
txHash: transaction.transactionHash,
note: '',
}),
),
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,23 @@ describe('Multisig Transaction note mapper (Unit)', () => {
expect(note).toBe(noteText);
});

it('should parse transaction `origin` and return a valid note', async () => {
const noteText = `<script>document.write('<img src=s onerror=alert("Hello World")>')</script>`;
const noteText2 = `<script>&#97;&#108;&#101;&#114;&#116;('Hello World')</script>`;
const transaction = (await multisigTransactionBuilder())
.with('origin', JSON.stringify({ note: noteText }))
.build();
const transaction2 = (await multisigTransactionBuilder())
.with('origin', JSON.stringify({ note: noteText2 }))
.build();

const note = mapper.mapTxNote(transaction);
const note2 = mapper.mapTxNote(transaction2);

expect(note).toBe('');
expect(note2).toBe('');
});

it('should return undefined if `origin` is not a valid JSON', async () => {
const transaction = (await multisigTransactionBuilder())
.with('origin', 'invalid-json')
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
import { Injectable } from '@nestjs/common';
import { MultisigTransaction } from '@/domain/safe/entities/multisig-transaction.entity';
import sanitizeHtml from 'sanitize-html';
import { ProposeTransactionDto } from '@/domain/transactions/entities/propose-transaction.dto.entity';

@Injectable()
export class MultisigTransactionNoteMapper {
mapTxNote(transaction: MultisigTransaction): string | null {
mapTxNote(
transaction: MultisigTransaction | ProposeTransactionDto,
): string | null {
if (transaction.origin) {
try {
const origin = JSON.parse(transaction.origin);
if (typeof origin.note === 'string') {
return origin.note;
return sanitizeHtml(origin.note as string, {
allowedAttributes: {},
allowedTags: [],
allowedIframeHostnames: [],
});
}
} catch {
// Ignore, no note
Expand Down
22 changes: 22 additions & 0 deletions src/routes/transactions/transactions.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import { TransferDetailsMapper } from '@/routes/transactions/mappers/transfers/t
import { TransferMapper } from '@/routes/transactions/mappers/transfers/transfer.mapper';
import { getAddress, isAddress } from 'viem';
import { LoggingService, ILoggingService } from '@/logging/logging.interface';
import { MultisigTransactionNoteMapper } from '@/routes/transactions/mappers/multisig-transactions/multisig-transaction-note.mapper';

@Injectable()
export class TransactionsService {
Expand All @@ -51,6 +52,7 @@ export class TransactionsService {
private readonly transactionPreviewMapper: TransactionPreviewMapper,
private readonly moduleTransactionDetailsMapper: ModuleTransactionDetailsMapper,
private readonly multisigTransactionDetailsMapper: MultisigTransactionDetailsMapper,
private readonly multisigTransactionNoteMapper: MultisigTransactionNoteMapper,
private readonly transferDetailsMapper: TransferDetailsMapper,
@Inject(LoggingService) private readonly loggingService: ILoggingService,
) {}
Expand Down Expand Up @@ -434,6 +436,9 @@ export class TransactionsService {
safeAddress: `0x${string}`;
proposeTransactionDto: ProposeTransactionDto;
}): Promise<TransactionDetails> {
args.proposeTransactionDto.origin = this.verifyOrigin(
args.proposeTransactionDto,
);
await this.safeRepository.proposeTransaction(args);

const safe = await this.safeRepository.getSafe({
Expand Down Expand Up @@ -534,4 +539,21 @@ export class TransactionsService {
): number | null {
return page.results.at(-1)?.nonce ?? null;
}

private verifyOrigin(transaction: ProposeTransactionDto): string | null {
if (transaction.origin) {
try {
const note = this.multisigTransactionNoteMapper.mapTxNote(transaction);

const origin = JSON.parse(transaction.origin);
origin.note = note;

return JSON.stringify(origin);
} catch {
// If the origin is not a valid JSON, we return null
}
}

return null;
}
}
125 changes: 124 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3249,6 +3249,15 @@ __metadata:
languageName: node
linkType: hard

"@types/sanitize-html@npm:^2":
version: 2.13.0
resolution: "@types/sanitize-html@npm:2.13.0"
dependencies:
htmlparser2: "npm:^8.0.0"
checksum: 10/a32c67bdf86048efa8ee5cdc1a68351758bae888956010eea58e694c1f9fc580632d1ce0478d31984559e0b03282d130ffe3e87479838c8da92cf1ac06626edc
languageName: node
linkType: hard

"@types/semver@npm:^7.5.8":
version: 7.5.8
resolution: "@types/semver@npm:7.5.8"
Expand Down Expand Up @@ -4944,6 +4953,44 @@ __metadata:
languageName: node
linkType: hard

"dom-serializer@npm:^2.0.0":
version: 2.0.0
resolution: "dom-serializer@npm:2.0.0"
dependencies:
domelementtype: "npm:^2.3.0"
domhandler: "npm:^5.0.2"
entities: "npm:^4.2.0"
checksum: 10/e3bf9027a64450bca0a72297ecdc1e3abb7a2912268a9f3f5d33a2e29c1e2c3502c6e9f860fc6625940bfe0cfb57a44953262b9e94df76872fdfb8151097eeb3
languageName: node
linkType: hard

"domelementtype@npm:^2.3.0":
version: 2.3.0
resolution: "domelementtype@npm:2.3.0"
checksum: 10/ee837a318ff702622f383409d1f5b25dd1024b692ef64d3096ff702e26339f8e345820f29a68bcdcea8cfee3531776b3382651232fbeae95612d6f0a75efb4f6
languageName: node
linkType: hard

"domhandler@npm:^5.0.2, domhandler@npm:^5.0.3":
version: 5.0.3
resolution: "domhandler@npm:5.0.3"
dependencies:
domelementtype: "npm:^2.3.0"
checksum: 10/809b805a50a9c6884a29f38aec0a4e1b4537f40e1c861950ed47d10b049febe6b79ab72adaeeebb3cc8fc1cd33f34e97048a72a9265103426d93efafa78d3e96
languageName: node
linkType: hard

"domutils@npm:^3.0.1":
version: 3.2.2
resolution: "domutils@npm:3.2.2"
dependencies:
dom-serializer: "npm:^2.0.0"
domelementtype: "npm:^2.3.0"
domhandler: "npm:^5.0.3"
checksum: 10/2e08842151aa406f50fe5e6d494f4ec73c2373199fa00d1f77b56ec604e566b7f226312ae35ab8160bb7f27a27c7285d574c8044779053e499282ca9198be210
languageName: node
linkType: hard

"dotenv-expand@npm:12.0.1":
version: 12.0.1
resolution: "dotenv-expand@npm:12.0.1"
Expand Down Expand Up @@ -5069,6 +5116,13 @@ __metadata:
languageName: node
linkType: hard

"entities@npm:^4.2.0, entities@npm:^4.4.0":
version: 4.5.0
resolution: "entities@npm:4.5.0"
checksum: 10/ede2a35c9bce1aeccd055a1b445d41c75a14a2bb1cd22e242f20cf04d236cdcd7f9c859eb83f76885327bfae0c25bf03303665ee1ce3d47c5927b98b0e3e3d48
languageName: node
linkType: hard

"env-paths@npm:^2.2.0":
version: 2.2.1
resolution: "env-paths@npm:2.2.1"
Expand Down Expand Up @@ -5976,6 +6030,18 @@ __metadata:
languageName: node
linkType: hard

"htmlparser2@npm:^8.0.0":
version: 8.0.2
resolution: "htmlparser2@npm:8.0.2"
dependencies:
domelementtype: "npm:^2.3.0"
domhandler: "npm:^5.0.3"
domutils: "npm:^3.0.1"
entities: "npm:^4.4.0"
checksum: 10/ea5512956eee06f5835add68b4291d313c745e8407efa63848f4b8a90a2dee45f498a698bca8614e436f1ee0cfdd609938b71d67c693794545982b76e53e6f11
languageName: node
linkType: hard

"http-cache-semantics@npm:^4.1.1":
version: 4.1.1
resolution: "http-cache-semantics@npm:4.1.1"
Expand Down Expand Up @@ -6272,6 +6338,13 @@ __metadata:
languageName: node
linkType: hard

"is-plain-object@npm:^5.0.0":
version: 5.0.0
resolution: "is-plain-object@npm:5.0.0"
checksum: 10/e32d27061eef62c0847d303125440a38660517e586f2f3db7c9d179ae5b6674ab0f469d519b2e25c147a1a3bc87156d0d5f4d8821e0ce4a9ee7fe1fcf11ce45c
languageName: node
linkType: hard

"is-promise@npm:4.0.0":
version: 4.0.0
resolution: "is-promise@npm:4.0.0"
Expand Down Expand Up @@ -7611,6 +7684,15 @@ __metadata:
languageName: node
linkType: hard

"nanoid@npm:^3.3.8":
version: 3.3.8
resolution: "nanoid@npm:3.3.8"
bin:
nanoid: bin/nanoid.cjs
checksum: 10/2d1766606cf0d6f47b6f0fdab91761bb81609b2e3d367027aff45e6ee7006f660fb7e7781f4a34799fe6734f1268eeed2e37a5fdee809ade0c2d4eb11b0f9c40
languageName: node
linkType: hard

"natural-compare@npm:^1.4.0":
version: 1.4.0
resolution: "natural-compare@npm:1.4.0"
Expand Down Expand Up @@ -7929,6 +8011,13 @@ __metadata:
languageName: node
linkType: hard

"parse-srcset@npm:^1.0.2":
version: 1.0.2
resolution: "parse-srcset@npm:1.0.2"
checksum: 10/d40c131cfc3ab7bb6333b788d30a30d063d76a83b49fa752229823f96475e36cf29fea09e035ce3b2a634b686e93e2a7429cb8dad0041d8a3a3df622093b9ea1
languageName: node
linkType: hard

"parse5-htmlparser2-tree-adapter@npm:^6.0.0":
version: 6.0.1
resolution: "parse5-htmlparser2-tree-adapter@npm:6.0.1"
Expand Down Expand Up @@ -8092,7 +8181,7 @@ __metadata:
languageName: node
linkType: hard

"picocolors@npm:^1.0.0, picocolors@npm:^1.1.0":
"picocolors@npm:^1.0.0, picocolors@npm:^1.1.0, picocolors@npm:^1.1.1":
version: 1.1.1
resolution: "picocolors@npm:1.1.1"
checksum: 10/e1cf46bf84886c79055fdfa9dcb3e4711ad259949e3565154b004b260cd356c5d54b31a1437ce9782624bf766272fe6b0154f5f0c744fb7af5d454d2b60db045
Expand Down Expand Up @@ -8143,6 +8232,17 @@ __metadata:
languageName: node
linkType: hard

"postcss@npm:^8.3.11":
version: 8.5.3
resolution: "postcss@npm:8.5.3"
dependencies:
nanoid: "npm:^3.3.8"
picocolors: "npm:^1.1.1"
source-map-js: "npm:^1.2.1"
checksum: 10/6d7e21a772e8b05bf102636918654dac097bac013f0dc8346b72ac3604fc16829646f94ea862acccd8f82e910b00e2c11c1f0ea276543565d278c7ca35516a7c
languageName: node
linkType: hard

"postgres-array@npm:~2.0.0":
version: 2.0.0
resolution: "postgres-array@npm:2.0.0"
Expand Down Expand Up @@ -8583,6 +8683,7 @@ __metadata:
"@types/jsonwebtoken": "npm:^9"
"@types/lodash": "npm:^4.17.14"
"@types/node": "npm:^22.13.1"
"@types/sanitize-html": "npm:^2"
"@types/semver": "npm:^7.5.8"
"@types/supertest": "npm:^6.0.2"
amqp-connection-manager: "npm:^4.1.14"
Expand All @@ -8602,6 +8703,7 @@ __metadata:
redis: "npm:^4.7.0"
reflect-metadata: "npm:^0.2.2"
rxjs: "npm:^7.8.1"
sanitize-html: "npm:^2.14.0"
semver: "npm:^7.6.3"
source-map-support: "npm:^0.5.20"
supertest: "npm:^7.0.0"
Expand Down Expand Up @@ -8632,6 +8734,20 @@ __metadata:
languageName: node
linkType: hard

"sanitize-html@npm:^2.14.0":
version: 2.14.0
resolution: "sanitize-html@npm:2.14.0"
dependencies:
deepmerge: "npm:^4.2.2"
escape-string-regexp: "npm:^4.0.0"
htmlparser2: "npm:^8.0.0"
is-plain-object: "npm:^5.0.0"
parse-srcset: "npm:^1.0.2"
postcss: "npm:^8.3.11"
checksum: 10/8e673f09ee33768537e5bf2dab45bb445c2313f6ce3f1cf6d48cdc5f60c122c9641cff2f5679a2f67c58b28b22e78265445919078360741512a42b0f4744a3e2
languageName: node
linkType: hard

"schema-utils@npm:^3.1.1, schema-utils@npm:^3.2.0":
version: 3.3.0
resolution: "schema-utils@npm:3.3.0"
Expand Down Expand Up @@ -8842,6 +8958,13 @@ __metadata:
languageName: node
linkType: hard

"source-map-js@npm:^1.2.1":
version: 1.2.1
resolution: "source-map-js@npm:1.2.1"
checksum: 10/ff9d8c8bf096d534a5b7707e0382ef827b4dd360a577d3f34d2b9f48e12c9d230b5747974ee7c607f0df65113732711bb701fe9ece3c7edbd43cb2294d707df3
languageName: node
linkType: hard

"source-map-support@npm:0.5.13":
version: 0.5.13
resolution: "source-map-support@npm:0.5.13"
Expand Down

0 comments on commit d1623f2

Please sign in to comment.