Skip to content

feat(security): Soroban deployment fingerprinting and contract upgrade detection - #1075

Merged
mijinummi merged 1 commit into
MDTechLabs:mainfrom
ThatCodeBabe:feat/soroban-contract-upgrade-detection-and-deployment-fingerprinting
Aug 28, 2026
Merged

feat(security): Soroban deployment fingerprinting and contract upgrade detection#1075
mijinummi merged 1 commit into
MDTechLabs:mainfrom
ThatCodeBabe:feat/soroban-contract-upgrade-detection-and-deployment-fingerprinting

Conversation

@ThatCodeBabe

Copy link
Copy Markdown
Contributor

Two closely related pieces of the same defence, so they ship together: fingerprinting establishes what an approved deployment is, and upgrade detection notices when a configured contract stops being it.

Closes #1066
Closes #1065


#1066 — Deployment fingerprinting

src/security/contracts/fingerprints/, src/contracts/verification/

A fingerprint is a sha256 over exactly the fields that define a deployment's identity — contract address, network, wasm hash, spec version, and a canonical digest of the interface surface. Observation-time context (observedAt, deployedAt, txHash, deployer) is deliberately excluded: the same deployment read from two nodes at two times must fingerprint identically, or every comparison is a false alarm.

Determinism is enforced where it can quietly break:

  • Ordering. The interface surface is sorted and de-duplicated before hashing. Two nodes can enumerate a contract spec in different orders; that is not an interface change and must not read as one.
  • Normalization. Addresses are upper-cased (base32), wasm hashes lower-cased with an optional 0x stripped.
  • Non-hex hashes are rejected. A base64 hash would fingerprint perfectly cleanly and then never match anything — a permanent silent mismatch is worse than a loud failure at the door.
  • Delimiter safety. The identity payload is a JSON array, not address|network|hash, so no field value can impersonate another deployment by containing the separator.
  • Networks are separate. The same contract id exists on testnet and public with different code; colliding them would let a testnet deployment satisfy a mainnet check.

ApprovedFingerprintStore holds what an operator approved, scoped per contract and network. Re-approving updates rather than duplicating (and un-revokes, since that is an explicit decision to trust again); revoked records are retained rather than deleted, because "approved and then revoked" is a materially different answer from "never heard of it". Persistence is left to the caller via snapshot() / restore().

SorobanDeploymentVerifier returns four distinct statuses rather than a boolean, because they call for different responses:

Status Means Response
approved matches an active approval proceed
mismatch contract has approvals, this deployment is not one investigate — result names what differs
revoked matches an approval since withdrawn stop the integration
unknown nothing ever approved here make an approval decision

#1065 — Upgrade detection

src/security/contracts/upgrades/soroban/, src/contracts/monitoring/

SorobanUpgradeDetector tracks configured contracts and compares each observation by fingerprint, not by any single field — so an upgrade that keeps the interface identical, the case most likely to go unnoticed, is still caught.

Indicators: wasm hash changed, spec version changed, functions added, functions removed, events changed, error codes changed, redeployed. Severity:

  • critical — a function was removed. A changed signature reads as one removed plus one added, which is correct: existing callers break.
  • major — new code behind an unchanged interface. Nothing visible changed, which is precisely why it would otherwise keep being trusted.
  • minor — spec version, additions, events or error codes only.
  • info — same code redeployed. Still surfaced by default: the wasm is unchanged but storage and admin need not be.

Every integration that depended on the previous deployment is flagged for review. A second upgrade while a flag is open does not raise a duplicate — it raises the existing flag to the worst severity seen, and never lowers it. Flags clear only when an operator re-approves, and cleared flags stay in the audit list.

ContractUpgradeMonitor owns scheduling and fan-out only:

  • Probes are injectable, so this is testable without a Horizon node.
  • A probe failure is not an upgrade. Tracked state is left untouched on error, so an unreachable node cannot look like a rollback.
  • Probe waits are bounded — a hung probe would otherwise stall every later check on the interval.
  • One notifier throwing does not stop the others, or the loop. A broken webhook is the worst possible reason to stop noticing contract upgrades.
  • When a fingerprint store is supplied, each observation is verified too and unapproved-deployment is emitted for anything not approved.

Verification

jest tests/security/contracts   116 passed (5 suites)
tsc --noEmit --strict           clean over the new modules
eslint + prettier               clean
pnpm run build                  clean

One thing worth flagging, since it affects how much the green check above is worth: CI runs pnpm run build and cargo test, and that build is nest build --path apps/api/tsconfig.json, whose include is ./src/**/*.ts and packages/utils/src/** — the repo-root src/ tree, where this work and most other feature code lives, is never compiled by CI, and jest never runs at all. So CI would be green for this PR whether or not the code compiles. That is why the numbers above are from running the suite locally. Happy to add a pnpm test step and widen the build in a separate PR if you want it — it seemed wrong to slip a CI change into a feature branch.

Implements MDTechLabs#1066 and MDTechLabs#1065.

MDTechLabs#1066 — Deterministic fingerprints for Soroban deployments over the
fields that define a deployment's identity: contract address, network,
wasm hash, spec version and a canonical digest of the interface surface.
Observation-time context is deliberately excluded so the same deployment
seen twice fingerprints identically. Approved fingerprints are stored
per contract and network, revocation is retained for audit, and the
verifier reports approved / mismatch / revoked / unknown separately with
the specific differences.

MDTechLabs#1065 — Upgrade detection compares each observation against the tracked
fingerprint, classifies what changed (wasm, spec version, functions
added or removed, events, error codes, redeploy), grades severity,
records the event, flags every integration that depended on the previous
deployment, and notifies the registered monitoring sinks. A removed
function is critical; new code behind an unchanged interface is major,
since nothing visible would otherwise stop it being trusted.

116 tests pass. tsc --strict and eslint are clean over the new files.
@drips-wave

drips-wave Bot commented Aug 28, 2026

Copy link
Copy Markdown

@ThatCodeBabe Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@mijinummi
mijinummi merged commit ef5c7f1 into MDTechLabs:main Aug 28, 2026
1 check passed
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.

Implement Stellar Contract Deployment Fingerprinting Implement Soroban Contract Upgrade Detection

2 participants