Drips: RepoDriver
Cantina Security Report
Organization
- @Drips
Engagement Type
Cantina Reviews
Period
-
Repositories
Findings
High Risk
1 findings
0 fixed
1 acknowledged
Medium Risk
5 findings
3 fixed
2 acknowledged
Low Risk
4 findings
1 fixed
3 acknowledged
Informational
10 findings
4 fixed
6 acknowledged
High Risk1 finding
Upgrade can strand legacy Gelato balances
State
- Acknowledged
Severity
- Severity: High
Submitted by
r0bert
Description
The previous
RepoDriverimplementation held native-token balances for Gelato fee payments. Users could deposit funds, keep a personal balance on the proxy, and withdraw any unused remainder throughwithdrawUserFunds. The Lit-based implementation removes that interface, while the upgrade proposal switches the proxy straight to the new implementation.If any deployment still has nonzero
userFundsor sharedcommonFundswhen the upgrade executes, the ETH stays in the proxy but the user-controlled withdrawal path disappears immediately. Nothing is stolen during the upgrade, but the funds become stranded behind governance or a follow-up rescue implementation instead of remaining withdrawable by the users who deposited them.This makes the migration unsafe unless every upgraded deployment can prove that those balances are already zero, or the upgrade is staged so users can exit first.
Recommendation
Do not execute the direct Gelato-to-Lit upgrade unless legacy fee balances are confirmed to be zero. The safer options are to enforce a pre-upgrade invariant on
userFundsTotalandcommonFunds, or to ship an intermediate implementation that disables new Gelato activity while keeping withdrawals open.If a direct upgrade must stay available, the proposal needs an on-chain migration or rescue path that preserves user-level withdrawal rights instead of converting recovery into an admin-only process.
Drips: Acknowledged. As of now across 4 deployments there are no tokens locked as
userFunds. There is a slim chance that in the window before the update somebody does actually lock funds, but in that case it's easier to reimburse them manually. Implementing a withdrawal path would increase complexity and introduce new risks while probably never being genuinely usable by anybody. We will monitor this.
Medium Risk5 findings
Reused Repo paths can seize accounts
State
- Acknowledged
Severity
- Severity: Medium
Submitted by
r0bert
Description
RepoDriverkeys GitHub and GitLab repositories by raw path bytes, not by a stable repository ID. If an old path is later reclaimed after a username, namespace, transfer, or path change, the new path controller can obtain a fresh oracle signature for the same(sourceId, name)pair and take over the existing Drips account. For example, if Alice changes her GitHub username fromalicetoalicehq, Bob can later registeralice, recreatealice/stream-paymentsand overwrite the owner of the original Drips account.Recommendation
Key repository accounts by stable upstream repository IDs instead of mutable path strings. If migration is not possible, treat path changes as explicit account migrations rather than as identity continuity.
Drips: Acknowledged. Documented in f08b4d6.
Pre-migration Lit payloads can override migrated accounts
Severity
- Severity: Medium
Submitted by
r0bert
Description
Before the Gelato-to-Lit upgrade, accounts only store an owner address. After the upgrade, the same storage is interpreted as
AccountOwner { address owner; uint32 timestamp; }, which leaves migrated legacy accounts with their existing owner buttimestamp == 0. Any Lit payload that was signed before the upgrade and carries a positive timestamp can therefore become the first acceptedupdateOwnerByLitwrite on that account, even if the off-chain ownership signal has already changed again. In practice, an outdated pre-migration Lit claim can overwrite the migrated owner until someone submits a newer Lit update.Recommendation
Gate the first Lit update for migrated legacy accounts so pre-migration payloads cannot be used after the upgrade. For example, require the first accepted Lit timestamp on migrated accounts to be at or after the migration activation time.
Drips: Fixed in f08b4d6.
Cantina: Fix verified.
Codeberg null suffix can revoke short accounts
Severity
- Severity: Medium
Submitted by
r0bert
Description
Short names collide under
bytes27(name)if they differ only by a trailing null byte. For thecodebergrepository source, the null-suffixed variant resolves to a meaningful404, which the oracle treats as revocation and signs as a zero-owner update. An attacker can therefore query the colliding null-suffixed name for a short Codeberg repo and repeatedly clear the owner of the original account without ever becoming the new owner.Recommendation
Use a length-preserving encoding or always hash
nameand reject null bytes before the oracle signs them.Drips: Fixed in f08b4d6. It's verified on the oracle side.
Cantina: Fix verified.
GitLab user/group reuse can seize accounts
State
- Acknowledged
Severity
- Severity: Medium
Submitted by
r0bert
Description
The Lit oracle assigns
sourceId = 8to bothgitLabUserandgitLabGroupandRepoDriverderives the controlled account only from(sourceId, name). As a result, a GitLab user account and a GitLab group with the same canonical path are treated as the same on-chain identity even though they are different upstream entity types and are proven through different oracle flows.This becomes exploitable because
updateOwnerByLitaccepts any oracle-signed payload whosetimestampis strictly newer than the last stored timestamp, but does not enforce any expiry or freshness window. ThegitLabUserpath signs with the personal access tokencreated_atvalue, so while an attacker still controls a GitLab username they can mint a fresh token, obtain a valid signature for that(sourceId, name)and keep it indefinitely. If the GitLab path is later reassigned to a group with the same name, the cached signature still targets the sameRepoDriveraccount and can be replayed to restore the attacker as the on-chain owner.Once the stale signature is submitted, the attacker regains control over the Drips account until a newer GitLab-derived update is posted. During that window they can call owner-gated functions such as
collect,give,setStreams,setSplitsandemitAccountMetadata, which is sufficient to drain funds or reconfigure future flows.Recommendation
Do not let GitLab users and GitLab groups share the same identity space. At minimum, assign them different
sourceIdvalues so a user path and a group path cannot resolve to the sameaccountId. For a durable fix, bind GitLab identities to stable upstream principal IDs instead of mutable path strings.Drips: Acknowledged. Won't fix, documented in f08b4d6. Users and groups intentionally share the same namespace, so the collision risk between a user and a group is no different from the existing collision risk between two users or two groups. A separate source ID would therefore not reduce the underlying risk.
Lit oracle chain-string padding collision lets attacker revoke RepoDriver ownership and flip RepoDeadlineDriver payout
Severity
- Severity: Medium
Submitted by
r0bert
Description
The Lit Action selects repo ownership claims by exact chain string equality but signs the EIP-712 payload using
bytes32(chain)viaformatBytes32String. Because bytes32-string encoding is right-zero-padded, distinct strings differing only by trailing NUL bytes (e.g., "sepolia" vs "sepolia\u0000") encode to the same on-chainbytes32. An attacker can request a NUL-padded chain string so the oracle finds no matching claim and signsowner = address(0)for abytes32value that still matchesRepoDriver.chain. Submitting this signature toRepoDriver.updateOwnerByLitrevokes an otherwise-claimed repo account and flipsRepoDeadlineDriver.collectAndGiveto the refund branch after the deadline, depriving intended recipients of funds.Recommendation
In
contracts/oracle/litAction.js, canonicalize requested chains to abytes32form first and use that canonical value consistently for de-duplication (prevent multiple strings mapping to the samebytes32), claim selection (compare bybytes32value, not raw string), and signing.Reject chain strings containing
\u0000and other non-printable characters.Consider changing
FUNDING.jsonto store chain identifiers asbytes32(or a collision-resistant hash) instead of free-form strings.Drips: Fixed in f08b4d6.
Cantina: Fix verified.
Low Risk4 findings
Hugging Face YAML can revoke valid owners
State
- Acknowledged
Severity
- Severity: Low
Submitted by
r0bert
Description
The Hugging Face lookup reads
ownedByfrom YAML frontmatter, and an unquoted0x...value is parsed as a number instead of a string. Address parsing then fails, but the lookup is still treated as meaningful, so the oracle can sign a fresh zero-owner update. A small README formatting mistake can therefore clear an existing owner until a corrected claim is posted.Recommendation
Require
ownedByto be a string before address validation, or parse frontmatter with a schema that preserves plain scalars as strings. Malformed claims should be rejected, not treated as revocation.Drips: Acknowledged. Documented in f08b4d6.
Oracle fetches have no response size limit
State
- Acknowledged
Severity
- Severity: Low
Submitted by
r0bert
Description
The fetch layer has no response-size limit and buffers the same body twice with
arrayBuffer()andtext(). An attacker-controlled website can return a very large response and force the Lit action to spend extra memory, time and fee budget on content that should have been rejected early. This makes claim refreshes less reliable and shifts avoidable work onto the caller.Recommendation
Enforce a small response-size cap and stop reading once it is exceeded. The fetch layer should read the response body only once, instead of first consuming
response.clone().arrayBuffer()and then consumingresponse.text()again on the original response.Drips: Acknowledged. Won't fix as there are other ways for the user to harm themselves and the potential damage is low.
GitHub case aliases split one identity
State
- Acknowledged
Severity
- Severity: Low
Submitted by
r0bert
Description
The GitHub sources sign the path exactly as the caller typed it. GitHub itself treats
octocat/spoon-knifeandOctoCat/SPOON-KNIFEas the same repository, butcalcAccountIdtreats those strings as different bytes and derives different Drips accounts from them. The same GitHub user or repo can therefore end up split across multiple accounts depending only on casing.Recommendation
Normalize GitHub names to one canonical form before they are signed and before
calcAccountIdis derived. The safest approach is to fetch the canonical owner and repo names from GitHub and always use that exact returned path, rather than trusting caller input. Do not silently apply that change to existing accounts, because the canonical path may hash to a different account ID than the mixed-case path users already rely on. Instead, introduce a new source version for canonicalized GitHub identities or provide a migration that moves ownership and configuration from the old mixed-case account to the new canonical one.Drips: Acknowledged. Documented in f08b4d6.
Consider longer timeout & retries
Severity
- Severity: Low
Submitted by
high byte
Description
During peak traffic, load times may take longer than 5 seconds, or temporarily reject or drop the response.
Recommendation
Use a longer timeout (default for timeout is 30 seconds) and add retries for retryable failures. A simple approach is a thirty second timeout with three attempts.
Drips: Fixed in f08b4d6.
Cantina: Fix verified.
Informational10 findings
Reused domain names can seize accounts
Severity
- Severity: Informational
Submitted by
r0bert
Description
The
websitesource keys accounts by the rawhost[/path]string and signs whatever controller currently serveshttps://<name>/FUNDING.json. If that domain expires, is transferred, or the hosted path changes hands, the new controller can take over the same Drips account. In practice, the account belongs to whoever controls that website today, not to a stable long-term identity.Recommendation
Consider documenting this behaviour. Otherwise, do not treat website accounts as persistent identities. If control of the domain or path changes, create a new account and migrate users and configuration to it instead of reusing the old one.
Drips: Fixed in f08b4d6 by documenting this behaviour.
Cantina: Fix verified.
GitLabUser claims break under DPoP
Severity
- Severity: Informational
Submitted by
r0bert
Description
The
gitLabUserflow authenticates with only thePRIVATE-TOKENheader. GitLab accounts that enable DPoP require an additionalDPoPproof header, so this source stops working for those users. Funds are not stolen, but legitimate owners may be unable to refresh or recover their claim through the GitLab user path.Recommendation
Add DPoP support to the GitLab user flow or mark DPoP-enabled accounts unsupported for this source. If unsupported, document that limitation clearly in the user flow.
Drips: Fixed in f08b4d6 by documenting this behaviour.
Cantina: Fix verified.
Zero-owner revocation triggered by HTTP states
State
- Acknowledged
Severity
- Severity: Informational
Submitted by
r0bert
Description
The oracle treats
401,403, and404as meaningful absence and defaults missing claims to the zero address when revocation is enabled. Temporary auth failures, rate limits, or provider-side policy changes can therefore produce a signed zero-owner update without proving that ownership was intentionally removed. Anyone can submit that payload and clear the account until a newer update arrives.Recommendation
Consider requiring a stronger evidence before signing revocation. Ambiguous HTTP failures should trigger retry or no-op, not immediate owner clearing.
Drips: Acknowledged. Partially fixed in f08b4d6. Added retrials when the response is not 2xx, 401, 403 or 404, which covers network failures and cases like HTTP 429 and 5xx errors that may go away on retrials.
Cantina: Fix verified. The fix does improve the transient-failure as it adds retries for non-2xx responses that are not 401/403/404, so 429, 5xx and network failures no longer immediately feed into revocation. But the core behavior remains: in
litAction, 401, 403 and 404 are still treated as “meaningful”, that flag still becomesrevokeUnclaimedand missing claims still default toAddressZero. So 401/403 can still produce a signed zero-owner update.Website names can rewrite fetch targets
State
- Acknowledged
Severity
- Severity: Informational
Submitted by
r0bert
Description
The
websitesource concatenates the rawnamestring into a URL without validation. Inputs containing userinfo, ports, queries, or fragments can therefore make the oracle fetch a different target than the signed website string suggests. This weakens the identity binding for website accounts and may also reach unexpected destinations.Recommendation
Parse and validate
nameas stricthost[/path]. Reject all other URL components and rebuild the request from validated parts.Drips: Acknowledged.
Token-based claim modes expand trust boundary
State
- Acknowledged
Severity
- Severity: Informational
Submitted by
r0bert
Description
The
gitLabUser,codebergUserandhuggingFaceUserflows require users to supply reusable third-party API tokens to the Lit execution environment. That expands the trust boundary beyond the user and the upstream provider. If the Lit environment or its assumptions fail, the leaked token can usually be used directly against the user's account.This should be fine given the Lit documentation, as requests are processed inside TEEs and the request contents are not exposed to operators during normal operation but is definitely a third party risk to consider:
- https://developer.litprotocol.com/learning-lit/communicating-with-lit-nodes
- https://developer.litprotocol.com/node-ops/requirements
Recommendation
Prefer proof flows that do not require reusable credentials. If these modes remain, disclose the trust tradeoff clearly and recommend the narrowest scope and shortest lifetime tokens possible.
Drips: Acknowledged. The minimum token privileges are documented.
General recommendation: use typescript
State
- Acknowledged
Severity
- Severity: Informational
Submitted by
high byte
Description
In the current codebase there are lots of validations and typechecks that can be done by leveraging typescript & libraries for example Axios to get typed structured data.
Drips: Acknowledged.
Gitlab uri encoding is incomplete
State
- Acknowledged
Severity
- Severity: Informational
Submitted by
high byte
Description
In Gitlab group claim the
nameUriis built by simply encoding slashes inname, but that is incomplete. If thenamecontains?or#symbols the remainder of the url will be improperly moved from path to query or hash respectively.Recommendation
Consider using
encodeURIComponent.Drips: Acknowledged.
Permissionless emitAccountId can spam events
State
- Acknowledged
Severity
- Severity: Informational
Submitted by
r0bert
Description
emitAccountIdis permissionless and emitsAccountIdSeenfor arbitrary caller-supplied inputs. Anyone can therefore spam the event stream with fake discovery signals. This does not grant ownership, but indexers and monitoring should treat the event as untrusted.Recommendation
Treat
AccountIdSeenonly as a hint unless it is paired with a trusted ownership update.Drips: Acknowledged.
Short name encoding collides on null suffix
Severity
- Severity: Informational
Submitted by
r0bert
Description
Short names are encoded with
bytes27(name), which right-pads with zeros and discards explicit trailing null bytes. Two distinct short byte strings can therefore collide if they differ only by a null suffix. Whether that is exploitable depends on the source, but the collision exists in the encoding itself.Recommendation
Reject names with null bytes or switch to a length-preserving encoding. Always hashing
namewould remove the short-name ambiguity entirely.Drips: Fixed in f08b4d6. It's verified on the oracle side.
Cantina: Fix verified.
Oracle CLI fails on current node
Severity
- Severity: Informational
Submitted by
r0bert
Description
The manual oracle tooling is documented as a normal operator workflow, but the CLI entrypoint does not start on the current local Node runtime. The entry script imports
mkdtempDisposablefromnode:fs/promisesat module load time and later uses it to create the temporary storage directory for Lit auth state. On the current environment, that export is not available, so Node aborts before any subcommand logic runs.The failure is easy to reproduce with the exact command the documentation tells operators to use for deployment inspection.
$ npm run getDeployment > [email protected] getDeployment> node index.js getDeployment file:///.../oracle/index.js:6import { mkdtempDisposable } from "node:fs/promises"; ^^^^^^^^^^^^^^^^^SyntaxError: The requested module 'node:fs/promises' does not provide an export named 'mkdtempDisposable'In local testing this happened on Node
v24.2.0. The same top-level import failure also prevents other documented commands from running, because the process dies before it ever dispatches togetDeployment,deposit,queryByName, orqueryByToken. This is not limited to the one path that actually needs temporary storage. As written, the whole CLI is coupled to a runtime-specific API at import time.That makes the operator story worse than it first appears. The README tells users to run
npm run getDeploymentto print the oracle IPFS hash and the derived signing addresses for each Lit network. That is exactly the kind of command a deployer, reviewer, or responder needs when checking which signer aRepoDriverinstance should trust. Instead, the tool crashes before it prints anything useful. The same goes for the manual query commands that are supposed to help users fetch a fresh signed ownership payload. If someone is trying to verify a deployment, rotate infrastructure, or recover from a stale owner record, the documented local tooling is unavailable until they first debug the Node compatibility problem.This is not an on-chain security bug, but it is a real operational break. The repository does not declare a strict supported Node version and the documentation only says that
npmis needed. A user following the documented setup can therefore end up with a nonfunctional CLI even though installation succeeds and the failure only appears at runtime.Recommendation
Stop depending on
mkdtempDisposableandawait usingin the CLI entrypoint. Usemkdtempto create the temporary directory, pass the resulting path into the Lit auth storage setup and clean it up in atry/finallyblock with an explicit removal step. That keeps the behavior the same without tying the tool to a narrow runtime feature.If the project intentionally requires a specific Node release, declare it explicitly in
package.jsonand in the README so operators fail early with a clear version requirement rather than a runtime import crash. A simple regression check in CI should run the documented commands, especiallynpm run getDeployment, on the supported Node version range so this kind of breakage is caught before release.Drips: Fixed in f08b4d6.
Cantina: Fix verified.