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
9 changes: 9 additions & 0 deletions .github/workflows/backend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,12 @@ jobs:

- name: Build backend
run: npm run build

- name: Upload source maps to Sentry
if: github.ref == 'refs/heads/main'
run: npx @sentry/cli releases files "$SENTRY_RELEASE" upload-sourcemaps ./dist
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: novasupport
SENTRY_PROJECT: novasupport-backend
SENTRY_RELEASE: ${{ github.sha }}
20 changes: 15 additions & 5 deletions backend/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -536,7 +536,7 @@ All errors return JSON with an \`error\` field and optional \`code\`:
.join("\n\n");

const body = [
`NETWORK_PASSPHRASE="${process.env.STELLAR_NETWORK === 'MAINNET'
`NETWORK_PASSPHRASE="${process.env.STELLAR_NETWORK === 'PUBLIC'
? 'Public Global Stellar Network ; September 2015'
: 'Test SDF Network ; September 2015'}"`,
`FEDERATION_SERVER="https://api.novasupport.xyz/federation"`,
Expand Down Expand Up @@ -1042,10 +1042,13 @@ All errors return JSON with an \`error\` field and optional \`code\`:
let orderBy: object = { createdAt: "desc" };

if (sort === "most_supported" || sort === "most_transactions") {
// For sorting by support metrics, we'll fetch all and sort in memory
// This is a simplified approach; for production, consider aggregation
// For sorting by support metrics, we fetch up to 1000 profiles to
// avoid loading unbounded rows into memory (#790). A production-grade
// solution should use a precomputed totalSupported column incremented
// transactionally. The take cap is a safe short-term mitigation.
const profiles = await prisma.profile.findMany({
where,
take: 1000,
include: {
acceptedAssets: true,
supportTransactions: {
Expand Down Expand Up @@ -3155,14 +3158,21 @@ All errors return JSON with an \`error\` field and optional \`code\`:
assetIssuer: parsed.data.assetIssuer,
};

// Verify the profile exists before touching Horizon (#574)
// Verify the profile exists and that recipientAddress matches its wallet
// before touching Horizon (#794). Without this check an attacker can
// supply a real tx hash paying their own wallet while pointing profileId
// at a victim — Horizon validation passes but the wrong profile is
// credited. Fetching walletAddress here closes that fraud vector.
const profileExists = await prisma.profile.findUnique({
where: { id: parsed.data.profileId },
select: { id: true },
select: { id: true, walletAddress: true },
});
if (!profileExists) {
return sendError(res, 404, "Profile not found");
}
if (profileExists.walletAddress !== parsed.data.recipientAddress) {
return sendError(res, 400, "recipientAddress does not match profile wallet", "ADDRESS_MISMATCH");
}

const skipHorizonValidation = process.env.SKIP_HORIZON_VALIDATION === "true";
if (skipHorizonValidation) {
Expand Down
3 changes: 2 additions & 1 deletion backend/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true,
"outDir": "dist",
"rootDir": "."
"rootDir": ".",
"sourceMap": true
},
"include": ["src/**/*.ts", "prisma/**/*.ts"]
}
Expand Down
Loading