ILN spans Soroban smart contracts, a TypeScript SDK, a CLI, an indexer, and a notifications service. This policy explains what to report, how to report it, and how maintainers triage and respond.
| Component | In scope | Primary references |
|---|---|---|
| Soroban contracts | Authorization, accounting, settlement, storage, upgrade, and governance defects | contracts, Threat Model, Access Control, Upgrade Guide |
| SDK | XDR encoding, transaction construction, signing flow, contract ID handling, and client-side validation defects | sdk, SDK Integration |
| CLI | Wallet profile handling, local secret storage, command validation, and network configuration defects | cli, CLI README |
| Indexer | API behavior, event ingestion, database handling, cache safety, and denial-of-service exposure | indexer |
| Notifications | Webhook subscription handling, HMAC signing, SSRF defenses, rate limiting, and circuit breaker behavior | notifications, notifications README |
| CI/CD and deployment scripts | Secret handling, deployment correctness, artifact integrity, and release automation | .github/workflows, scripts |
Out of scope: spam, social engineering, physical attacks, attacks requiring compromised maintainer machines, and findings that only affect unsupported local configurations without a protocol or user-impact path.
- Missing or incorrect authorization checks.
- Reentrancy-like control-flow mistakes across contract calls or token transfers.
- Storage key collision, stale storage, or incorrect storage lifetime handling.
- Incorrect invoice state transitions, settlement math, discount-rate math, or reputation updates.
- Governance bypasses, quorum mistakes, timelock bypasses, or unsafe upgrade paths.
- SAC integration mistakes, token decimal assumptions, or trustline-related accounting errors.
- Denial-of-service paths that permanently block valid invoice, settlement, or governance actions.
- Incorrect XDR encoding or decoding that changes contract arguments or return values.
- Signing bypass, signing the wrong transaction envelope, or network-passphrase confusion.
- Contract ID, account, or asset validation bugs that route funds or calls incorrectly.
- Unsafe secret handling in helpers such as keypair signers.
- Misleading errors that cause callers to retry unsafe transactions or ignore failed submissions.
- Secret leakage through logs, stack traces, profile files, or command output.
- Incorrect encryption/decryption or PIN handling for wallet profiles.
- Network configuration bugs that cause mainnet/testnet/local confusion.
- Commands that submit unintended transactions or skip required confirmation.
- SQL injection or unsafe query construction.
- API abuse, resource exhaustion, or unbounded request amplification.
- Incorrect event parsing that reports false invoice or reputation state.
- Cache poisoning or stale data presented as finalized state.
- Exposure of local database files, internal errors, or operational metadata.
- HMAC bypass, signature confusion, replay exposure, or unsigned payload delivery.
- SSRF through webhook URLs, redirects, DNS rebinding, or internal network targets.
- Rate-limit bypass or circuit-breaker bypass.
- Subscription authorization mistakes or cross-tenant data disclosure.
- Email delivery abuse or injection in notification content.
Send reports to security@invoice-liquidity-network.local or open a private GitHub Security Advisory for this repository.
Include as much as possible:
- Affected component and commit, tag, branch, or deployed contract ID.
- Steps to reproduce.
- Expected behavior and actual behavior.
- Impact assessment, including affected assets, users, or permissions.
- Proof-of-concept code, transaction XDR, logs, screenshots, or traces.
- Whether you believe the issue is actively exploitable.
Do not include live secrets, private keys, or personally identifiable information unless maintainers explicitly request a secure transfer method.
| Stage | Commitment |
|---|---|
| Acknowledgment | Within 48 hours. |
| Initial severity assessment | Within 5 business days. |
| Critical target fix window | Begin mitigation immediately; target patch or disabling mitigation within 7 days. |
| High target fix window | Target patch within 14 days. |
| Medium target fix window | Target patch within 30 days. |
| Low target fix window | Track for the next planned maintenance release or documentation update. |
| Public disclosure | Coordinated after a fix, mitigation, or maintainer-approved advisory timeline. |
Timelines can change if a fix requires third-party coordination, contract migration, or user action. Maintainers will keep reporters updated when timelines change.
| Severity | Description | Examples |
|---|---|---|
| Critical | Direct loss or theft of user funds, permanent protocol insolvency, or unauthenticated upgrade/admin takeover. | Unauthorized settlement, draining escrow, bypassing governance to upgrade contracts. |
| High | Material fund risk, broad data integrity failure, secret exposure, or reliable service compromise. | SDK signing bypass, incorrect contract call XDR, webhook HMAC bypass with sensitive impact, SQL injection that modifies indexed state. |
| Medium | Limited financial or operational impact, denial of service with recovery path, or scoped data exposure. | Localized invoice-state misreporting, rate-limit bypass, recoverable governance workflow disruption. |
| Low | Defense-in-depth issue, documentation security gap, low-impact information exposure, or hard-to-exploit edge case. | Misleading error message, missing hardening header, low-risk dependency advisory. |
| Informational | No immediate exploit path but useful for hardening. | Suggestions for stricter validation, logging improvements, or clearer runbooks. |
We will not pursue legal action or request law-enforcement investigation for good-faith security research that:
- Avoids privacy violations, data destruction, extortion, and service disruption.
- Uses testnet, local deployments, or reporter-owned accounts whenever possible.
- Stops testing and reports promptly after discovering a plausible vulnerability.
- Does not move, drain, or lock funds that do not belong to the reporter.
- Gives maintainers reasonable time to investigate and remediate before public disclosure.
Safe harbor does not cover social engineering, phishing, physical attacks, spam, malware, or attacks against third-party services outside ILN's control.
- Confirm receipt and assign a private tracking owner.
- Reproduce the issue on a local, testnet, or isolated environment.
- Assign severity using the table above.
- Prepare mitigation, patch, test, and migration steps.
- Coordinate disclosure with the reporter.
- Publish advisory notes when appropriate, including affected versions, impact, fixed versions, and user actions.
This checklist guides auditors and maintainers through a structured security review of contract, SDK, and infrastructure changes.
- All state-mutating functions explicitly check caller identity or role via
require_auth(). - Administrative functions (pause, fee updates, admin changes) are protected by a single-signature or multisig gate.
- LP and payer operations respect role isolation (LP cannot approve themselves without queue resolution, payers cannot modify terms).
- Insurance pool admin role is restricted to the configured contract address; no user can claim without admin authorization.
- All Soroban SDK method calls (
invoke_contract,transfer,invoke_host_function) are preceded by caller validation. - Audit command:
cargo test --manifest-path contracts/invoice_liquidity/Cargo.toml -- --nocapture | grep -E "(auth|unauthorized)"
- All arithmetic operations that could overflow use checked methods or explicit bounds.
- Discount calculations clamp discount rate between 0 and max (configurable, default 10000 bps).
- LP yield and LP payout are bounded by invoice amount; no calculation can produce negative or zero-exceeding payouts.
- Premium and balance arithmetic in the insurance pool uses checked addition/subtraction.
- Score calculations bound results to 0-100 range; no overflow from weighting.
- Audit command:
cargo clippy --manifest-path contracts/invoice_liquidity/Cargo.toml -- -W clippy::arithmetic_side_effects
- No external contract calls are made before state updates (checks-effects-interactions pattern).
- Invoice state transitions are atomic; partial updates roll back on error.
- LP yield claims and default refunds are processed in a single transaction; split claims are not possible.
- Recursive contract calls (e.g., nested
invoke_contractin event handlers) are not made. - Insurance pool claims cannot be re-triggered for the same invoice (
is_claimedprevents double-processing). - Audit command:
grep -r "invoke_contract" contracts/ | grep -v "tests" | wc -l
- Storage key structure avoids collisions (enums with address/ID suffixes for per-user keys).
- Storage lifetime (instance vs persistent) is appropriate (admins/config in instance; per-user in persistent).
- Ledger expiration is configured (TTL for persistent keys, instance keys are immortal).
- No parallel mutation of the same invoice state during queue resolution or funding.
- Stale storage reads are prevented by checking expected invariants (e.g., invoice must exist before update).
- Audit command:
rg "DataKey::" contracts/invoice_liquidity/src/ | sort | uniq -c | sort -rn
- Discount rate calculations use a configured static max, not real-time oracle prices (oracle reads are not live pricing).
- Reputation score calculations are deterministic based on historical invoice state, not time-dependent.
- No timestamp-dependent price or exchange-rate assumptions in accounting (all payments are in native tokens).
- Fund queue resolution selects the highest-reputation LP in a single atomic transaction; no two LPs can be selected for the same invoice.
- Default detection does not rely on external oracle timestamps; invoices expire by absolute
dueDate. - Audit command:
grep -r "env.ledger\(\).timestamp\(\)" contracts/ | wc -l
- Oracle calls (if any) are wrapped in error handlers that degrade gracefully.
- Pulled prices are checked against sane bounds (e.g., XLM price bounded 0.05-5 USD for testnet).
- Oracle endpoint changes are gated by governance (ProposalAction::UpdateOracle requires proposal + vote).
- Prices used in discount or settlement math are from a single consistent oracle call per transaction.
- Audit command:
grep -r "get_price\|oracle\|price_feed" contracts/ --include="*.rs" | grep -v "test" | wc -l
- Contract upgrade requires admin authorization via
set_admin()and wasm upload proposal. - Storage schema is versioned; upgrade code validates old state before mutation.
- No breaking changes to storage keys or enum discriminants without a migration step.
- Invoices in-flight during an upgrade maintain their state; no data is lost.
- Paused contracts cannot be upgraded; admin must explicitly unpause post-upgrade and validate state.
- Audit command:
grep -r "Migration\|Upgrade\|version" contracts/invoice_liquidity/src/ --include="*.rs" | head -20
- Invoice amounts, LP yields, LP payouts, and default refunds sum to zero (conservation of funds).
- Partial payments increment
amountPaidbut do not changeamountFunded; funding and payment are independent. - Discount rate logic is deterministic:
effectiveYieldBps = max(discountRate, minYield) * (daysUntilDue / 365)(no time-dependent rounding). - Default compensation is bounded by coverage cap and pool balance.
- All settlement math uses integer arithmetic (stroops); decimal representation is in SDK, not contract.
- Audit command:
grep -A 10 "amountPaid\|amountFunded\|yield\|payout" contracts/invoice_liquidity/src/settlement.rs 2>/dev/null | head -30
- Pause/unpause operations can be called only by admin; no unpause delay.
- Governance proposals require a vote quorum and timeout; no single vote can pass.
- Rejected proposals cannot be re-executed or re-voted on.
- Admin changes emit an event; no silent handoff.
- Paused invoices cannot be paid, funded, or claimed; paused pool cannot process new premiums.
- Audit command:
grep -r "pause\|unpause" contracts/ --include="*.rs" | grep -v "test" | wc -l
- Batch operations (if implemented) construct a single Soroban transaction envelope with multiple invoke operations.
- Network passphrase is validated against the RPC server before signing.
- XDR encoding of contract arguments (addresses, amounts, timestamps) is verified by decoding and re-checking.
- Signer is stored in client config and passed consistently; no global signer state.
- Addresses are validated as valid Stellar G… format before submission.
- Amounts are checked for non-negative values and no overflow.
- Timestamps are reasonable (not in the distant past or future).
- Contract ID is validated as a Soroban contract address (not an account).
Run these commands regularly to catch common issues:
# Rust formatting and linting
cd contracts && cargo fmt --check && cargo clippy -- -D warnings
# Run all contract tests with coverage
cargo test --manifest-path contracts/invoice_liquidity/Cargo.toml --release
# Security-focused clippy rules
cargo clippy --manifest-path contracts/invoice_liquidity/Cargo.toml -- \
-W clippy::arithmetic_side_effects \
-W clippy::panicking_unwrap \
-W clippy::unimplemented
# Check for common Soroban security issues
rg "unwrap\(\)|expect\(" contracts/ --type rust | grep -v test | wc -l
rg "env.events().publish\(" contracts/ --type rust | wc -l
rg "require_auth\(\)" contracts/ --type rust | grep -v test | wc -l
# Ensure all state mutations are guarded
rg "storage.*set\(" contracts/ --type rust | wc -l
rg "require_auth|assert" contracts/ --type rust | grep -v test | wc -l
# Validate SDK type definitions
cd sdk && npm run build && npm run test
# Lint and format TypeScript
cd sdk && npm run fmt:check && npm run lint- Contract changes include authorization, storage layout, and state-transition review.
- SDK and CLI changes include transaction, signing, and network-passphrase tests.
- Indexer changes include input-validation and API-abuse review.
- Notifications changes include HMAC, SSRF, rate-limit, and circuit-breaker tests.
- CI confirms Rust tests, Node tests, formatting, linting where available, and coverage thresholds.
- All items in the Comprehensive Security Audit Checklist above are reviewed before mainnet release.
- Mainnet releases require the Mainnet Launch Checklist to be signed off.
| Function | External Call(s) | CEI Pattern | Guard | Risk |
|---|---|---|---|---|
fund_invoice |
token.transfer(funder→contract) |
✅ State check before | lock_reentrancy |
Low — SAC has no callback |
fund_invoice |
oracle.get_payer_data |
✅ Read-only, before state | — | None — view only |
fund_invoice |
token.transfer(contract→freelancer) |
lock_reentrancy |
Low | |
fund_invoice |
distribution.accrue_lp |
✅ After state save | — | Low — trusted contract |
mark_paid |
token.transfer(payer→contract) |
lock_reentrancy |
Low | |
mark_paid |
token.transfer(contract→admin) |
lock_reentrancy |
Low | |
mark_paid |
token.transfer(contract→funder) |
lock_reentrancy |
Low | |
mark_paid |
distribution.accrue_settlement |
✅ After state save | — | Low — trusted contract |
cancel_invoice |
token.transfer(contract→funder) |
lock_reentrancy |
Low | |
claim_default |
token.transfer(contract→funder) |
lock_reentrancy |
Low | |
resolve_dispute |
token.transfer(contract→funder) |
lock_reentrancy |
Low | |
execute_proposal (governance) |
invoke_contract(iln_contract, ...) |
— | Low — governance trust boundary |
-
Reentrancy guards (
lock_reentrancy/unlock_reentrancy): Added tofund_invoice,mark_paid,cancel_invoice,claim_default, andresolve_dispute. Uses a boolean instance storage lock; on error/panic the lock is automatically cleared by Soroban's revert semantics. -
CEI pattern enforcement: All state updates in
mark_paid,claim_default,cancel_invoice, andresolve_disputenow occur before external token transfers. Previously the status/amount updates were after token transfers, which could have allowed reentrant exploitation with exotic tokens. -
Oracle calls: The
oracle.get_payer_datacall infund_invoiceis a read-only view and occurs only after auth checks and state validation. State mutation happens after the oracle returns. -
Distribution contract calls:
notify_distribution_fundingandnotify_distribution_settlementare informational updates to a trusted distribution contract. They are invoked after state is fully persisted.
- Governance
execute_proposaliniln_governanceinvokes the ILN contract after changingProposalStatus::Passedbut beforeProposalStatus::Executed. This is an accepted design choice because the ILN contract's admin functions are idempotent and guarded byrequire_admin. - Soroban's host function isolation provides inherent reentrancy protection for SAC token transfers, so the guards are defense-in-depth.
Rate limiting is applied to sensitive admin functions to prevent griefing via rapid parameter changes. See docs/access-control.md#6-rate-limiting-design-issue-541 for the full design, cooldown table, and implementation details.
Key design decisions:
- Cooldown is measured in ledgers (not timestamps), aligning with Soroban's deterministic execution model.
- Emergency functions (
pause,unpause,resolve_appeal,resolve_dispute) are exempt. - Rate limits are per-function, so calling
update_fee_ratedoes not affectupdate_max_discount. - Each rate-limited function is keyed by a
Symbolin instance storage (DataKey::RateLimit(Symbol)).