Files
insights/.semgrep/rules/security/tooltip-link-needs-doclink-or-interactive.yaml
zeekay 1b367e2b62 chore: rebase the tree onto upstream master
The tree was a debranded snapshot of upstream taken around 2026-02-20, and
upstream has moved about five months since. This replaces it with upstream
master, mechanically re-derived: bin/debrand does the rename, and the
pipeline and squashed migrations are restored from the shipped tree.

Not a merge. The previous import replaced the tree wholesale in a single
commit, so there is no shared content history to merge against. Reproducing
the rename on the new tree is what actually converges.

What survives: .hanzo/workflows, the Dockerfiles and bin/docker* entrypoints,
compose files, LLM.md, and insights/migrations. The squashed initial
migration is load-bearing — the live database records it by name, so
upstream's ~918 individual migrations cannot come back.

Nine products are dropped as unreferenced. Twenty-one more that we do not run
are kept anyway because the core app still imports them; unpicking those is a
refactor and does not belong in a rebase.

This base does not build yet, and the reasons are not in the rename:
upstream now wants Python 3.13.13 against our 3.12.12 and Django 5.2 against
our 4.2, and the dependency manifests still name upstream packages that we
republish under our own names. Those are decisions, not renames.
2026-08-01 15:44:40 -07:00

70 lines
3.5 KiB
YAML

rules:
- id: tooltip-link-needs-doclink-or-interactive
message: >-
This `<Tooltip>` renders a `<Link>` inside its `title` prop but has neither
`docLink` nor `interactive` set. The shared Tooltip defaults `interactive`
to `false`, which passes `disableHoverablePopup={true}` to base-ui — the
popup is not a valid hover target, so the cursor leaves the trigger before
it can reach the link. The link is effectively unreachable.
Fix:
- If the link is a Insights docs URL, use the `docLink` prop. It auto-enables
`interactive`, renders the canonical "Read the docs" link, and attaches
the standard `clicked tooltip doc link` autocapture:
`<Tooltip docLink="https://hanzo.ai/docs/..." title="...">...</Tooltip>`
- If the link points to an internal app page (settings, urls.*) or fires
an action, add `interactive` so the popup becomes hoverable:
`<Tooltip interactive title={<>... <Link to={...}>...</Link></>}>...</Tooltip>`
Note: `closeDelayMs` extends the close timer but does NOT make the popup
hoverable. It is not a substitute for `interactive`.
If a particular call site is intentionally non-hoverable (e.g. the Link is
purely decorative and unreachable on purpose), add a `// nosemgrep: tooltip-link-needs-doclink-or-interactive`
comment above it with a brief justification.
severity: ERROR
languages: [typescript, javascript]
patterns:
# Match <Tooltip ... title={$TITLE} ...>...</Tooltip>, then run a
# fresh Semgrep search inside the binding of $TITLE looking for a
# <Link> or <a>. metavariable-pattern descends through the JSX AST,
# so this catches Links nested at any depth inside the title
# expression — including the audit case with two <div> layers
# between the Fragment root and the <Link>.
- pattern: <Tooltip ... title={$TITLE} ...>$...CHILDREN</Tooltip>
- metavariable-pattern:
metavariable: $TITLE
patterns:
- pattern-either:
- pattern: <Link ...>...</Link>
- pattern: <a ...>...</a>
# Structural `pattern-not: <Tooltip ... interactive ... title={$T} ...>`
# in Semgrep does not reliably expand `...` to zero JSX attributes,
# so call sites where `interactive`/`docLink` is the first prop are
# not excluded. Drop to a text-level exclusion: if the matched
# source text contains `interactive` or `docLink` as a whole word,
# the developer has acknowledged hoverability — even an explicit
# `interactive={false}` is a deliberate choice, and `interactive={dynamic}`
# at least signals intent. `closeDelayMs` is intentionally NOT in
# this list: it extends the close timer but does not make the popup
# hoverable, so tooltips relying on it as a workaround still fire.
- pattern-not-regex: '\binteractive\b'
- pattern-not-regex: '\bdocLink\b'
paths:
include:
- 'frontend/'
- 'products/'
exclude:
- '**/*.test.tsx'
- '**/*.test.ts'
- '**/*.spec.tsx'
- '**/*.spec.ts'
- '**/*.stories.tsx'
metadata:
category: correctness
technology:
- react
- insights