Files
c6fc377a83 feat(web): Security hub (#7839)
* feat(web): add SECURITY_HUB feature flag and route plumbing

Adds SECURITY_HUB to the FEATURES enum, registers the value in MSW
chain fixtures (mainnet + all), bumps the gateway schema hash, marks
/spaces/security as a recognised space route, and applies minor
PageLayout/useRouterGuard touch-ups.

Foundation for the SecurityHub feature; subsequent stacked PRs build on
top of this.

Part 1/8 of the security-hub split (originally PR #7820).

* feat(security): scanner infrastructure  (#7840)

* feat(security): scanner infrastructure — types, utils, constants, test helpers

Introduces the core scanner module shape that subsequent stacked PRs
plug into:

- types: ScanContext, ScanResult, SecurityScanner contract
- constants: thresholds, version data, environment guards
- utils: computeSummary, severityRank, scanKey, formatTimestamp
- test-helpers: shared scaffolding for scanner unit tests

Forward refs to securityTypes/securityScoring resolve in PR 4/8.

Part 2/8 of the security-hub split (originally PR #7820).

* fix(web): move definitions to contants and types

* refactor(security): address scanner-infra review feedback

- Move SCANNER_TIMEOUT_MS from utils.ts to constants.ts (review #3217986422)
- Drop unused local ZERO_ADDRESS — consumers can import it from
  @safe-global/utils/utils/constants when needed (review #3217891868)
- Normalize address in scanKey via toLowerCase so checksummed and
  lowercase inputs yield the same key (review #3217958268)
- Add tests for withScannerTimeout and getSafeGrade (review body)

* feat(security): individual safe-state scanners + registry (#7841)

* feat(security): individual safe-state scanners + registry

Implements the 11 scanners that inspect a Safe and produce ScanResults:

- accountSetup, multichainSetup, contractVersion
- modules, guard, fallbackHandler, factoryValidation
- pendingTx, recovery, transactionScanning, signerIntegrity

Plus the registry that aggregates them into the SCANNERS array consumed
by the scoring engine. Each scanner ships with its own unit tests,
plus a shared defaultValues regression test.

Builds on PR 2/8 (scanner infra). Feature public API lands in PR 4/8.

Part 3/8 of the security-hub split (originally PR #7820).

* refactor(security): address safe-state-scanner review feedback

accountSetup: extract baseEvidence, recommend 2+ for 1/N threshold
  (review #3218059833, #3218097369)
guard: rename local isHypernativeGuard to nameSuggestsHypernativeGuard,
  tighten to startsWith (review #3219227551). Add getSeverityFromScore
  helper to constants (review #3218213953).
fallbackHandler: import TWAP_FALLBACK_HANDLER(_NETWORKS) from
  @/features/swap (review #3219147109). Move handlerLabel above the
  match check so both branches reuse it (review #3219191990).
modules: extract ALLOWANCE_MODULE_VERSIONS (review #3219257831) and
  trusted/untrusted evidence arrays (review #3219273672).
recovery: replace substring "delay" check with exact match against
  DELAY_MODIFIER_NAMES (review #3219297536).
Source ZERO_ADDRESS from @safe-global/utils/utils/constants across all
  scanners (carries over from PR 7840's local-const removal).

* fix(security): import TWAP constants from helpers/utils to avoid cycle

Importing from '@/features/swap' pulls createFeatureHandle into the
scanner module graph, which causes a circular dependency with
@/features/__core__ when consumers (e.g. SecurityHub tests) mock the
core module. Importing directly from helpers/utils keeps the dedup
intent of review #3219147109 while sidestepping the feature handle.

* feat(security): scoring engine + feature public API (#7842)

* feat(security): scoring engine + feature public API

Wraps the scanners from PR 3/8 into a consumable feature:

- securityTypes / securityChecks: dimension definitions and CheckStatus
  taxonomy that scanners and the UI share.
- securityScoring: aggregates ScanResult[] into per-Safe SecurityGrade.
- feature/contract/index/testing/types: the feature-architecture public
  surface (createFeatureHandle, exported types, testing stubs).
- SECURITY_CHECKS_MATRIX.md: the source-of-truth matrix mapping each
  scanner to severity/weight/grade.

Resolves PR 2/8's forward refs to securityTypes and securityScoring.
Hooks consuming this API land in PR 5/8.

Part 4/8 of the security-hub split (originally PR #7820).

* refactor(security): unify scoring thresholds in a single source of truth

Replace the duplicated ternary thresholds in getGrade / getStrengthLevel
with a single SCORING_THRESHOLDS table that pairs each clear-ratio
threshold with both a SecurityGrade and a StrengthLevel. Resolves
review #3219362052 on PR 7842.

* fix(security): repair stale imports after ZERO_ADDRESS / SafeGrade moves

PR 7840's review fixes dropped ZERO_ADDRESS from the local
scanners/constants.ts and moved SafeGrade out of scanners/utils.ts into
scanners/types.ts. The feature contract/types files were added on this
branch (PR 7842) and still imported from the old locations — silently
breaking module resolution at runtime, which prevented the SecurityHub
sidebar entry from rendering.

- feature.ts / contract.ts: import ZERO_ADDRESS from
  @safe-global/utils/utils/constants instead of the local constants
- types.ts: re-export SafeGrade from data/scanners/types (where it
  lives now); GradeSummary stays in data/scanners/utils

* feat(security): scan + feature-redirect hooks (#7843)

* feat(security): scan + feature-redirect hooks

Adds the React hooks that consume the scoring engine from PR 4/8:

- features/security/hooks/useSecurityScan: runs SCANNERS against a Safe
  and exposes ScanResult[] with cache + loading metadata.
- features/security/hooks/useSecurityHubFeatureRedirect: gates the
  /spaces/security route behind the SECURITY_HUB flag.
- features/spaces/hooks/useAutoScan: queues per-Safe scans across a
  space with throttling + cancellation.
- features/spaces/hooks/useSafeScanContext: builds the per-Safe
  ScanContext required by the scanners (RTK Query data, chain, owners,
  threshold, etc).

Each hook ships with unit tests. SecurityHub container that orchestrates
these lands in PR 6/8.

Part 5/8 of the security-hub split (originally PR #7820).

* refactor(security): address review feedback on scan hook + cache

- Extract scanResultsCache into its own module with private Map and
  get/set accessors so consumers can't mutate it by reference.
- Prefer newer timestamp on cache writes so a slow scan can't overwrite
  a faster, fresher one.
- Tighten SecurityScanner.id to a ScannerId literal union.
- Replace `err: any` with `err: unknown` in useSecurityScan catch.
- Fix initial-render progress reading 100% by counting settled
  scanners (results + errors) over total, not (total - loadingCount).

Updates feature.ts/contract.ts/useAutoScan callers to the new
setCachedScan/getCachedScan API.

* feat(security-hub): SecurityHub container + orchestrator hooks (#7844)

* feat(security-hub): SecurityHub container + orchestrator hooks

Adds the SecurityHub feature container under features/spaces, plus the
four hooks that drive its state machine:

- Page / index / types / utils: container entrypoint and shared shape
- useAutoScanOrchestrator: schedules and reconciles per-Safe scans
- useReconciledSpaceSafes: zips space membership with chain Safe state
- useReportDrawer: drawer open/close + selected scan target
- useScanResultsState: aggregates ScanResults across Safes for the panel

Forward refs to SecurityPanelView / SecurityReportDrawer /
SecuritySafesTable / WorkspaceHealthCard resolve in PR 7/8.

Part 6/8 of the security-hub split (originally PR #7820).

* refactor(security-hub): address review feedback on container hooks

- useReportDrawer: use sameAddress for address comparisons and require
  the selected chain to be one of the safe's chainEntries so multichain
  groups don't match across chains.
- utils.flattenSafes: drop the mainnet fallback for empty multichain
  groups — skip them instead so we don't synthesise a bogus chain that
  would queue scans against a non-existent Safe.
- Migrate AutoScanServices wiring to the new setCachedScan API
  introduced in part 5.

* feat(security-hub): UI components — panel, drawer, table, health card (#7845)

* feat(security-hub): UI components — panel, drawer, table, health card

Adds the four visual components rendered inside the SecurityHub
container (PR 6/8):

- WorkspaceHealthCard: aggregate space score + grade chips + re-scan
- SecuritySafesTable: per-Safe row table with status / score / actions
- SecurityPanelView: shell + primitives + useSecurityChecks state hook
- SecurityReportDrawer: accordion-based drawer with per-check CTAs

Each component ships with its Storybook story, story-derived snapshot
test, and __snapshots__ file. Adds container-level tests for
SecurityPanelView and SecuritySafesTable that exercise the wired-up flow.

Last piece before the routing PR (8/8) wires this into the sidebar.

Part 7/8 of the security-hub split (originally PR #7820).

* tests(security-hub): align SecuritySafesTable test scanKey with real impl

The test's local scanKey helper used `${address}:${chainId}` but
security.scanKey lowercases the address. The mismatch meant
scanResults[key] resolved to undefined inside the component, so cells
rendered dashes instead of either skeletons (scanning state) or evidence
values — failing two assertions.

* feat(security-hub): wire SecurityHub into sidebar + routing

Final integration PR — connects the SecurityHub container from PR 6/8
and its panel components from PR 7/8 to the rest of the app:

- pages/spaces/security.tsx: Next.js page entry
- features/spaces/components/Sidebar/config: registers Security item
- features/spaces/components/SpaceSidebarNavigation: nav item + active
  state handling
- features/spaces/contract.ts / feature.ts: feature-flag plumbing

After this PR is merged on top of 1-7, the resulting state is byte-for-byte
identical to PR #7820.

Part 8/8 of the security-hub split (originally PR #7820).

* chore(web): move badge component to its own folder

* refactor(security-hub): extract SafeGradeChip + shared grade tokens

Move the SafeGrade label and palette out of StatusCell and the
WorkspaceHealthCard filter chips into a single primitive at
components/SafeGradeChip/. The primitive owns the soft-vs-active variant
and label/palette lookup; both consumers reduce to a one-liner
(StatusCell static, WHC interactive with active + onClick).

* refactor(security): colocate SAFE_GRADE_RANK with SEVERITY_RANK

Move SAFE_GRADE_RANK from SecuritySafesTable.tsx into the canonical
scanner constants file, right next to SEVERITY_RANK. The two ranks
operate over different unions (SafeGrade vs SecurityGrade); sitting them
side by side with a docblock makes the distinction obvious at the
declaration site.

* refactor(security-hub): dedupe SEVERITY_RANK + split SecuritySafesTable

- primitives.tsx: drop the duplicate SEVERITY_RANK and import the canonical
  one from data/scanners/constants.
- SecuritySafesTable: extract cells (ScoreCell / ThresholdCell / VersionCell
  / BalanceCell), helpers (formatBalance, getEvidence, getAggregateSummary,
  getAggregateSafeGrade, hasMultichainWarning, isAnyChainScanning), and
  visual constants (DASH, ROW_VARIANTS, MotionTableRow, MotionTbody) into
  three sibling files. The main file drops from 684 → 482 lines.

* refactor(security-hub): extract row components from SecuritySafesTable

Pull the two row variants and the table chrome out of the main file:

- SingleSafeRow.tsx: single-chain Safe row.
- MultichainSafeRow.tsx: collapsed parent row + per-chain children rendered
  when expanded (child row lives as a sub-component in the same file since it
  is tightly coupled to the parent).
- constants.tsx: add TABLE_SX and the COLUMNS list so the table chrome config
  sits beside the other visual constants.

SecuritySafesTable.tsx is now 148 lines (was 684 originally) and just owns
the feature handle, animation/expand state, grade filter, and the
single-vs-multichain dispatch. Storybook snapshot tests pass unchanged.

* refactor(security-hub): split SecurityPanelView into view + hooks

Pull the panel apart along the view/logic seam:

Views (sibling files):
- SectionPanel.tsx — animated section wrapper.
- PanelHeader.tsx — score gauge + strength chip + action line.
- SignersSection.tsx — signers + multichain accordion.
- SecurityChecksSection.tsx — security-checks accordion.

Business logic (hooks/ subfolder):
- usePanelHeader.ts — derives score/level/color/actionLine from results.
- useSignerRows.tsx — signer + multichain row bucketing and sorting.
- useSecurityChecks.tsx — moved here; per-check row builder.

Plus a constants.ts holding STRENGTH_DESCRIPTIONS / GRADE_BG_BY_STRENGTH /
ROW_STAGGER. SecurityPanelView.tsx drops from 421 → 58 lines and now
just composes the three sub-sections + skeleton fallback. Storybook
snapshot tests pass unchanged.

* refactor(security-hub): address review feedback on panel components

- Use maybePlural for signer / check / issue plurals
- buildExpanded reuses isPassingStatus (inconclusive no longer shows remediation)
- useSecurityChecks memoizes the failing/passing row derivation
- MultichainSafeRow uses sameAddress for the selection check
- Derive SecuritySafesTable's rank → grade lookup from SEVERITY_RANK
- Re-export SEVERITY_RANK and SAFE_GRADE_RANK from @/features/security so
  primitives.tsx and SecuritySafesTable/utils.ts stop reaching into the
  feature's internals

---------

---------

---------

---------

---------

* feat(web): reuse severityScore utility function

* fix(web): remove unnecessary file

---------

* fix(web): remove console.log

---------

Co-authored-by: Hanzo Dev <[email protected]>
2026-05-18 12:25:06 +02:00

112 lines
2.9 KiB
JSON

{
"chainId": "1",
"chainName": "Ethereum",
"description": "",
"chainLogoUri": "https://safe-transaction-assets.staging.5afe.dev/chains/1/chain_logo.png",
"l2": false,
"isTestnet": false,
"zk": false,
"nativeCurrency": {
"name": "Ether",
"symbol": "ETH",
"decimals": 18,
"logoUri": "https://safe-transaction-assets.staging.5afe.dev/chains/1/currency_logo.png"
},
"transactionService": "https://api.5afe.dev/tx-service/eth",
"blockExplorerUriTemplate": {
"address": "https://etherscan.io/address/{{address}}",
"txHash": "https://etherscan.io/tx/{{txHash}}",
"api": "https://api.etherscan.io/v2/api?chainid=1&module={{module}}&action={{action}}&address={{address}}&apiKey={{apiKey}}"
},
"beaconChainExplorerUriTemplate": {
"publicKey": null
},
"disabledWallets": ["socialSigner", "trezor"],
"ensRegistryAddress": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e",
"balancesProvider": {
"chainName": "ethereum",
"enabled": true
},
"contractAddresses": {
"safeSingletonAddress": null,
"safeProxyFactoryAddress": null,
"multiSendAddress": null,
"multiSendCallOnlyAddress": null,
"fallbackHandlerAddress": null,
"signMessageLibAddress": null,
"createCallAddress": null,
"simulateTxAccessorAddress": null,
"safeWebAuthnSignerFactoryAddress": null
},
"features": [
"BRIDGE",
"COUNTERFACTUAL",
"CSV_TX_EXPORT",
"DEFAULT_TOKENLIST",
"DELETE_TX",
"DOMAIN_LOOKUP",
"EARN",
"EIP1271",
"EIP1559",
"ERC721",
"EURCV_BOOST",
"HYPERNATIVE",
"LENDING",
"MASS_PAYOUTS",
"MIXPANEL",
"MULTI_CHAIN_SAFE_ADD_NETWORK",
"MULTI_CHAIN_SAFE_CREATION",
"NATIVE_COW_SWAP_FEE_V2",
"NATIVE_SWAPS",
"NATIVE_SWAPS_COW",
"NATIVE_SWAPS_FEE_ENABLED",
"NATIVE_SWAPS_USE_COW_STAGING_SERVER",
"NATIVE_WALLETCONNECT",
"NESTED_SAFES",
"NEW_MOBILE_APP_AVAILABLE",
"PORTFOLIO_ENDPOINT",
"POSITIONS",
"PROPOSERS",
"PUSH_NOTIFICATIONS",
"RECOVERY",
"RISK_MITIGATION",
"SAFE_APPS",
"SAP_BANNER",
"SECURITY_HUB",
"SPACES",
"SPENDING_LIMIT",
"STAKE_TEASER",
"STAKING",
"STAKING_BANNER",
"TX_NOTES",
"TX_SIMULATION",
"ZODIAC_ROLES"
],
"gasPrice": [
{
"type": "oracle",
"uri": "https://api.etherscan.io/v2/api?chainid=1&module=gastracker&action=gasoracle&apikey=JNFAU892RF9TJWBU3EV7DJCPIWZY8KEMY1",
"gasParameter": "FastGasPrice",
"gweiFactor": "1000000000.000000000"
}
],
"publicRpcUri": {
"authentication": "NO_AUTHENTICATION",
"value": "https://cloudflare-eth.com"
},
"rpcUri": {
"authentication": "API_KEY_PATH",
"value": "https://mainnet.infura.io/v3/"
},
"safeAppsRpcUri": {
"authentication": "API_KEY_PATH",
"value": "https://mainnet.infura.io/v3/"
},
"shortName": "eth",
"theme": {
"textColor": "#ffffff",
"backgroundColor": "#627EEA"
},
"recommendedMasterCopyVersion": "1.4.1"
}