Skip to content

Security hardening: recordCompany auth, CEI, Ownable2Step+timelock, EAS membership, namehash, bounded loops - #57

Open
HabacucMX wants to merge 1 commit into
mainfrom
hardening/contract-and-eas-security
Open

Security hardening: recordCompany auth, CEI, Ownable2Step+timelock, EAS membership, namehash, bounded loops#57
HabacucMX wants to merge 1 commit into
mainfrom
hardening/contract-and-eas-security

Conversation

@HabacucMX

@HabacucMX HabacucMX commented Jul 13, 2026

Copy link
Copy Markdown
Member

Applies the transferable hardening patterns from the AxoloDAO best-of-4 contract review to StartupChain. The two products stay fully decoupled — no shared code, only the patterns transfer. Sepolia only, no mainnet moves.

forge test61 passing (13 new StartupChain.t.sol, 2 new AttestationModule.t.sol, existing suites unchanged). TS typecheck clean.

Contract fixes (each with a proving test)

  1. [HIGH] recordCompany caller auth — caller must be an owner of the target Safe (ISafe.isOwner) and the Safe must be deployed. Closes the ensNameToCompanyId squat/front-run vector. test_recordCompany_squatByNonOwnerReverts
  2. [HIGH] Admin → Ownable2Step + timelock — 2-step ownership; setFeeRecipient behind a 2-day propose→execute timelock. Deploy the owner as a 2/3 Safe. test_ownershipIsTwoStep, test_setFeeRecipientIsTimelocked
  3. [HIGH] CEI + reentrancy in recordCompany — all state written before the fee .call; nonReentrant. test_recordCompany_revertingFeeRecipientRevertsWholeTx
  4. [HIGH] Harden AttestationModule — real onlyCompanyMember (registry isFounder); schema setters owner-gated (were permissionless one-time). testOnlyCompanyMemberCanAttest, testSchemaSetterIsOwnerGated
  5. [MED] Correct .eth namehash — labels parented under namehash("eth"), not the ENS root, in transferENS/createSubdomain/revokeSubdomain. Unblocks Create subdomains for company members #43/Manage domains and subdomains from dashboard #44/Forward and reverse name resolution for ENS domains on Startupchain UI #33. test_namehash_isEthParentedNotRoot
  6. [MED] Bounded founder loops + reverse indexMAX_FOUNDERS = 50; founder → companyId[] index replaces the off-chain O(n) scan; isFounder backs Revert "Feat/branding on home" #4. test_maxFoundersEnforced, test_founderReverseIndex

Off-chain fixes

  1. [MED] Bind payment to the registrationcheckPaymentStatusAction requires the payment to come from a founder wallet (allowedFrom) + optional per-registration commitment hook. Residual: full single-use needs a consumed-tx store or on-chain msg.value fee (flagged).
  2. [MED] De-risk the hot relayer keySTARTUPCHAIN_TREASURY_ADDRESS separates the treasury from the gas-paying signer (falls back with a warning).

Tradeoffs (documented, not enforced)

  • Solo companies get a 1/1 Safe (allowed, documented — no floor, so solo founders aren't blocked).
  • Subdomain creation needs the contract approved as an ENS operator by the Safe (namehash fixed here; operator/module setup documented).
  • The founder reverse-index is append-only (confirm current membership via isFounder).

Not in this PR

Item #9 — moving the cap table off contract storage to a permanent Arweave snapshot + EAS anchor — is a separate, larger PR.

See SECURITY-HARDENING.md. Constructors changed: StartupChain(ens, resolver, feeRecipient, initialOwner), AttestationModule(eas, registry, initialOwner).

Greptile Summary

This PR hardens the StartupChain registration and attestation flow. The main changes are:

  • Safe-owner authorization for company registration.
  • Ownable2Step admin controls and a timelocked fee-recipient update.
  • Reentrancy protection and CEI ordering in recordCompany.
  • EAS membership checks through the StartupChain registry.
  • Correct .eth namehashing for ENS operations.
  • Bounded founder lists and a founder-to-company reverse index.
  • Founder-bound off-chain payment verification and treasury configuration.

Confidence Score: 4/5

The payment hardening path needs fixes before merging.

  • A valid payment tx can be reused for another registration with the same founder set.
  • Missing treasury configuration can route user payments to the hot relayer key.
  • The reviewed contract changes otherwise follow the intended hardening direction.

src/app/(app)/dashboard/setup/actions.ts, src/lib/blockchain/startupchain-client.ts

Security Review

The contract hardening reduces several on-chain risks, but the off-chain payment path still allows same-founder tx replay because the commitment is unused. Treasury misconfiguration can also route user prepayments to the hot signer.

Important Files Changed

Filename Overview
src/contracts/src/StartupChain.sol Adds Safe-owner registration checks, reentrancy protection, timelocked fee-recipient updates, corrected ENS node calculation, bounded founder loops, and founder indexing.
src/contracts/src/AttestationModule.sol Adds Ownable2Step schema administration and gates attestations through StartupChain founder membership.
src/app/(app)/dashboard/setup/actions.ts Passes founder wallets into payment verification but does not bind the payment tx to the specific registration.
src/app/(app)/dashboard/setup/payment-actions.ts Verifies payment status, destination, value, and founder sender, with an optional commitment check that the live flow does not use.
src/lib/blockchain/startupchain-client.ts Adds configurable treasury support but keeps a fallback to the hot signer address.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
  participant Founder
  participant Setup as commitEnsRegistrationAction
  participant Payment as checkPaymentStatusAction
  participant RPC as Sepolia RPC
  participant Treasury

  Founder->>Treasury: Send ETH payment
  Founder->>Setup: Submit registration and tx hash
  Setup->>Payment: tx hash, min value, allowed founders
  Payment->>RPC: Read receipt and transaction
  RPC-->>Payment: status, to, from, value, input
  Payment-->>Setup: confirmed when status/to/value/from pass
  Note over Payment: No commitment is supplied, so the tx is not tied to this ENS registration
  Setup-->>Founder: Continue registration
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
  participant Founder
  participant Setup as commitEnsRegistrationAction
  participant Payment as checkPaymentStatusAction
  participant RPC as Sepolia RPC
  participant Treasury

  Founder->>Treasury: Send ETH payment
  Founder->>Setup: Submit registration and tx hash
  Setup->>Payment: tx hash, min value, allowed founders
  Payment->>RPC: Read receipt and transaction
  RPC-->>Payment: status, to, from, value, input
  Payment-->>Setup: confirmed when status/to/value/from pass
  Note over Payment: No commitment is supplied, so the tx is not tied to this ENS registration
  Setup-->>Founder: Continue registration
Loading

Reviews (1): Last reviewed commit: "Security hardening: recordCompany auth, ..." | Re-trigger Greptile

Greptile also left 2 inline comments on this PR.

Context used:

  • Context used - AGENTS.md (source)

…AS membership, namehash, bounded loops

Applies the AxoloDAO best-of-4 hardening patterns to StartupChain (zero code coupling; patterns only). Sepolia only.

Contracts: #1 recordCompany requires Safe-owner caller (squat-revert); #2 Ownable2Step + 2-day timelock on setFeeRecipient; #3 CEI + nonReentrant in recordCompany (reverting-fee test); #4 real onlyCompanyMember (isFounder) + owner-gated schema setters; #5 correct .eth namehash in transferENS/create/revokeSubdomain; #6 MAX_FOUNDERS cap + founder->companyId index. Off-chain: #7 bind payment to a founder wallet (+ commitment hook); #8 separate treasury from the hot signer key. forge test: 61 passing. See SECURITY-HARDENING.md; item #9 (Arweave cap-table) left as a separate PR.
@vercel

vercel Bot commented Jul 13, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
startupchain Error Error Jul 13, 2026 7:25pm

Comment on lines 204 to 208
const paymentStatus = await checkPaymentStatusAction({
paymentTxHash,
minValueWei: totalWei,
allowedFrom: founderStructs.map((f) => f.wallet),
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security Registration Payment Remains Reusable

This call enables founder binding but never supplies the new expectedCommitment, so the check in payment-actions.ts is skipped for the live registration path. A founder can reuse one successful treasury tx hash for another ENS registration with the same founder set, because the server verifies from, to, status, and value but never ties the tx to this specific ensName or marks it consumed.

Comment on lines +183 to +187
console.warn(
'[startupchain] STARTUPCHAIN_TREASURY_ADDRESS is not set — falling back to the hot signer address ' +
'as treasury. Set a distinct treasury (ideally a Safe) before production (#8).'
)
return account.address

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security Treasury Falls Back To Signer

When STARTUPCHAIN_TREASURY_ADDRESS is unset or invalid, this branch accepts user prepayments into the hot relayer account and only logs a warning. The payment verifier then treats that signer address as the treasury, so a misconfigured deployment keeps fee custody and gas signing on the same key.

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.

1 participant