Skip to content
Merged
23 changes: 20 additions & 3 deletions .github/workflows/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@ jobs:
run: npm ci

- name: Run npm audit (production dependencies)
run: npm audit --omit=dev --audit-level=critical
run: npm audit --omit=dev --audit-level=high

- name: Check for known vulnerabilities in frontend (production dependencies)
run: npm audit --workspace=frontend --omit=dev --audit-level=critical
run: npm audit --workspace=frontend --omit=dev --audit-level=high

- name: Check for known vulnerabilities in backend (production dependencies)
run: npm audit --workspace=backend --omit=dev --audit-level=critical
run: npm audit --workspace=backend --omit=dev --audit-level=high

- name: Setup Rust toolchain for contract audit
uses: dtolnay/rust-toolchain@stable
Expand All @@ -62,6 +62,23 @@ jobs:
- name: Verify security setup
run: npm run verify-security

cargo-audit:
name: Cargo Dependency Vulnerability Scan
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable

- name: Install cargo-audit
run: cargo install cargo-audit

- name: Run cargo audit on contracts
run: cargo audit
working-directory: contracts

codeql-analysis:
name: CodeQL Analysis
runs-on: ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"dependencies": {
"@prisma/adapter-pg": "^7.8.0",
"@prisma/client": "^7.8.0",
"@stellar/stellar-sdk": "^15.1.0",
"@stellar/stellar-sdk": "^17.0.1",
"cors": "^2.8.6",
"dotenv": "^17.4.2",
"express": "^5.2.1",
Expand Down
4 changes: 2 additions & 2 deletions backend/src/middleware/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,15 +194,15 @@ export function verifyChallenge(req: Request, res: Response): void {

// Verify the manage_data op contains our nonce
const op = tx.operations[0] as StellarSdk.Operation.ManageData | undefined;
if (!op || op.type !== 'manageData' || op.value?.toString('hex') !== challenge.nonce) {
if (!op || op.type !== 'manageData' || Buffer.from(op.value ?? new Uint8Array()).toString('hex') !== challenge.nonce) {
res.status(401).json({ error: 'Invalid challenge nonce in transaction' });
return;
}

const keypair = StellarSdk.Keypair.fromPublicKey(publicKey);
const txHash = tx.hash();
const valid = tx.signatures.some((s) => {
try { return keypair.verify(txHash, s.signature()); } catch { return false; }
try { return keypair.verify(txHash, s.signature); } catch { return false; }
});

if (!valid) {
Expand Down
26 changes: 13 additions & 13 deletions backend/src/services/sorobanService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,25 +168,25 @@ export interface ChainStream {
}

export function decodeI128(val: xdr.ScVal): string {
const parts = val.i128();
const hi = BigInt.asIntN(64, BigInt(parts.hi().toString()));
const lo = BigInt.asUintN(64, BigInt(parts.lo().toString()));
const parts = (val as xdr.ScValI128).i128;
const hi = BigInt.asIntN(64, BigInt(parts.hi.toString()));
const lo = BigInt.asUintN(64, BigInt(parts.lo.toString()));
return ((hi << 64n) | lo).toString();
}

export function decodeAddress(val: xdr.ScVal): string {
const addr = val.address();
if (addr.switch().value === xdr.ScAddressType.scAddressTypeAccount().value) {
return StrKey.encodeEd25519PublicKey(addr.accountId().ed25519());
const addr = (val as xdr.ScValAddress).address;
if (addr.type === 'scAddressTypeAccount') {
return StrKey.encodeEd25519PublicKey((addr.accountId as xdr.PublicKeyEd25519).ed25519.value);
}
const hash = addr.contractId();
return StrKey.encodeContract(Buffer.from(hash as unknown as Uint8Array));
const hash = (addr as xdr.ScAddressContract).contractId;
return StrKey.encodeContract(Buffer.from(hash.value as unknown as Uint8Array));
}

function decodeMap(val: xdr.ScVal): Record<string, xdr.ScVal> {
const result: Record<string, xdr.ScVal> = {};
for (const entry of val.map() ?? []) {
result[entry.key().sym().toString()] = entry.val();
for (const entry of (val as xdr.ScValMap).map ?? []) {
result[(entry.key as xdr.ScValSymbol).sym.toString()] = entry.val;
}
return result;
}
Expand Down Expand Up @@ -321,8 +321,8 @@ export async function getStreamFromChain(streamId: bigint): Promise<ChainStream

const isActiveVal = fields['is_active']!;
const isActive =
isActiveVal.switch().value === xdr.ScValType.scvBool().value &&
isActiveVal.b() === true;
isActiveVal.type === 'scvBool' &&
isActiveVal.b === true;

return {
streamId,
Expand All @@ -332,7 +332,7 @@ export async function getStreamFromChain(streamId: bigint): Promise<ChainStream
ratePerSecond: decodeI128(fields['rate_per_second']!),
depositedAmount: decodeI128(fields['deposited_amount']!),
withdrawnAmount: decodeI128(fields['withdrawn_amount']!),
startTime: Number(fields['start_time']!.u64().toString()),
startTime: Number((fields['start_time']! as xdr.ScValU64).u64.toString()),
isActive,
};
} catch (err) {
Expand Down
28 changes: 14 additions & 14 deletions backend/src/workers/soroban-event-worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,20 @@ import { rpcPool } from "../lib/rpc-pool.js";

/** Decode an ScVal symbol to a string. */
export function decodeSymbol(val: xdr.ScVal): string {
return val.sym().toString();
return (val as xdr.ScValSymbol).sym.toString();
}

/**
* Decode an ScVal U64 to a JavaScript bigint.
* `xdr.UInt64` extends Long; `.toString()` gives the decimal representation.
*/
export function decodeU64(val: xdr.ScVal): bigint {
return BigInt(val.u64().toString());
return BigInt((val as xdr.ScValU64).u64.toString());
}

/** Decode an ScVal U32 to a JavaScript number. */
export function decodeU32(val: xdr.ScVal): number {
return val.u32();
return (val as xdr.ScValU32).u32;
}

/**
Expand All @@ -36,9 +36,9 @@ export function decodeU32(val: xdr.ScVal): number {
* Full value = hi * 2^64 + lo.
*/
export function decodeI128(val: xdr.ScVal): string {
const parts = val.i128();
const hi = BigInt.asIntN(64, BigInt(parts.hi().toString()));
const lo = BigInt.asUintN(64, BigInt(parts.lo().toString()));
const parts = (val as xdr.ScValI128).i128;
const hi = BigInt.asIntN(64, BigInt(parts.hi.toString()));
const lo = BigInt.asUintN(64, BigInt(parts.lo.toString()));
return ((hi << 64n) | lo).toString();
}

Expand All @@ -47,13 +47,13 @@ export function decodeI128(val: xdr.ScVal): string {
* string.
*/
export function decodeAddress(val: xdr.ScVal): string {
const addr = val.address();
if (addr.switch().value === xdr.ScAddressType.scAddressTypeAccount().value) {
return StrKey.encodeEd25519PublicKey(addr.accountId().ed25519());
const addr = (val as xdr.ScValAddress).address;
if (addr.type === 'scAddressTypeAccount') {
return StrKey.encodeEd25519PublicKey((addr.accountId as xdr.PublicKeyEd25519).ed25519.value);
}
// addr.contractId() returns a Hash (Opaque[]); cast to Uint8Array for encodeContract
const hash = addr.contractId();
return StrKey.encodeContract(Buffer.from(hash as unknown as Uint8Array));
// addr.contractId is a Hash (Opaque[]); cast to Uint8Array for encodeContract
const hash = (addr as xdr.ScAddressContract).contractId;
return StrKey.encodeContract(Buffer.from(hash.value as unknown as Uint8Array));
}

/**
Expand All @@ -62,10 +62,10 @@ export function decodeAddress(val: xdr.ScVal): string {
*/
export function decodeMap(val: xdr.ScVal): Record<string, xdr.ScVal> {
const result: Record<string, xdr.ScVal> = {};
const entries = val.map();
const entries = (val as xdr.ScValMap).map;
if (!entries) return result;
for (const entry of entries) {
result[entry.key().sym().toString()] = entry.val();
result[(entry.key as xdr.ScValSymbol).sym.toString()] = entry.val;
}
return result;
}
Expand Down
Loading
Loading