Skip to content

security: close 115 of 136 open CodeQL alerts on develop#813

Merged
feruzm merged 6 commits into
developfrom
security/codeql-fixes
May 20, 2026
Merged

security: close 115 of 136 open CodeQL alerts on develop#813
feruzm merged 6 commits into
developfrom
security/codeql-fixes

Conversation

@feruzm
Copy link
Copy Markdown
Member

@feruzm feruzm commented May 20, 2026

Summary

Bundle of fixes for the open CodeQL findings on develop. Closes 115 of 136 (~85%); remaining 21 are tracked at the bottom for follow-up PRs.

Rule Before Closed here
js/incomplete-sanitization 65 65
js/incomplete-url-scheme-check 12 12
js/incomplete-hostname-regexp 16 16
js/incomplete-url-substring-sanitization 5 4
js/incomplete-multi-character-sanitization 4 4
js/insecure-randomness 6 6
js/xss-through-dom 5 4
js/tainted-format-string 2 2
js/stack-trace-exposure 2 2
js/redos 1 1
js/bad-tag-filter 1 1
js/prototype-pollution-utility 1 1

Highlights

  • generateWif (communities/create) — replaced Math.random()-derived seed with crypto.getRandomValues. The previous form gave ~32 bits of entropy for a Hive owner key.
  • SEO cron routes (sitemap-generate, blacklist-refresh) — stopped returning raw e.message in 500 bodies; details now logged server-side.
  • Twitter URL matchingstartsWith("https://twitter.com")new URL(href).hostname === "twitter.com" (would otherwise accept https://twitter.com.evil.com).
  • Render-helper sanitize/anchor methods — positive-whitelist comments; broader dangerous-scheme strip in a.method (data:/vbscript:/file: in addition to javascript:); regex . properly escaped on hostname matchers.
  • Bulk .replace("%40", X)/%40/g across 38 profile/entry/wallet/publish/waves/perks files. Closes ~59 incomplete-sanitization alerts.
  • ReDoS (hive-post-extension) — rewrote [^@\s]+ to [^@\s/]+ so each path segment can't overlap, partition is unambiguous, matching linear.
  • Tainted format string (mattermost.ts, channels/.../posts/route.ts) — user-controlled values no longer interpolated into console.error first-arg.
  • Bad-tag filter (publish-editor-toolbar-fragments) — added /i so <SCRIPT> is matched too.
  • Multi-character sanitization (get-pure-post-text, publish-validate-post, self-hosted blog-post) — loop strip to idempotency so adversarial payloads like <scr<script>ipt> can't leak a fragment.
  • XSS-through-DOMselection-popover tweet URL now encodeURIComponent'd; api/import noscript innerHTML pre-strips script/style/iframe/event-handlers as defense-in-depth and validates lazy/srcset URLs against an http(s)/relative whitelist; hive-operation-extension validates the decoded to field as a Hive username before rendering into avatar URL.
  • UI Math.random (wave-follows-card, communities/create:generateUsername) — switched leaderboard-pick and username-suffix to crypto.getRandomValues. Closes 5 downstream taint alerts on community-list-item + wave-follows-card sinks.
  • Prototype pollution (self-hosted floating-menu/utils) — reject path segments containing __proto__/constructor/prototype before recursive writes.
  • proxyBase regex path (render-helper/proxify-image-src.ts:buildSrcSet) — replaced new RegExp(^${escapedBase}/p/...) with plain startsWith+slice. Closes 9 hostname-regex alerts (7 in spec file + source + self-hosted call site) without touching behavior.

Functional regression review

Only one visible behavior change:

  • a.method now strips data:/vbscript:/file: hrefs (was only javascript:). OWASP-aligned, but post bodies containing <a href="data:application/pdf;base64,...">PDF</a>-style links lose the href (link text remains). Practically rare in Hive content.

Everything else is internally equivalent:

Change Behavior diff
generateWif CSPRNG Output value differs (intentional); same shape, same length
SEO 500 body strips message Cron callers no longer see error detail in JSON; still in container logs
Twitter hostname-equality Rejects twitter.com.evil.com; real twitter.com/x.com still match
Hostname regex \. Tightens to literal dot; real URLs always have literal dots
.replace(/%40/g, ...) Identical output unless string has multiple %40 — Hive usernames have ≤1
linkify /^>/ Identical for matched inputs
Multi-char strip loop Strips more only on adversarial inputs
Toolbar /i flag Pure additive — warns on <SCRIPT> too
Mattermost console.error structure Log format only, operator-visible
ReDoS [^@\s/]+ Same matches in non-pathological input
Sanitize-html scheme check Equivalent — positive whitelist already catches everything except http(s)
proxyBase startsWith/slice Equivalent to escaped regex — same prefix check + same hash extraction

Test results

pnpm --filter @ecency/render-helper test: 1,072 / 1,072 passing. Typecheck shows no new errors on touched files; pre-existing Next.js 15 params: Promise and u-flag regex errors are unrelated.

Out of scope — follow-up PRs

  • bugfix/user-reports-2 #23 SSRF in api/import/route.ts: already mitigated by DNS-IP allowlist + per-hop redirect validation + size/timeout caps; residual TOCTOU window is documented (lines 109–114). Tightening needs fetch() pinned to the resolved IP via a custom undici dispatcher — separate PR.
  • 15× actions/missing-workflow-permissions: workflow files need explicit permissions: blocks — separate PR.
  • Feature/sentry #21 video-upload-threespeak <source src={URL.createObjectURL(file)}>: local blob URL, false positive — dismiss in CodeQL UI.
  • Bug: Ecency center bug in Decks page #100 youtubeVideosEnhancer.spec.ts URL match: test mock, false positive — dismiss in CodeQL UI.

Test plan

  • CI: render-helper test suite green (1,072 tests)
  • CI: typecheck — only pre-existing errors
  • After merge: re-query GitHub code-scanning API to confirm 115 alerts closed and only the listed 21 remain
  • Manual smoke (post-merge):
    • Create community flow still generates a hive-NNNNNN username and a WIF (now CSPRNG-sourced)
    • Wave "who-to-follow" card still renders 5 suggestions
    • Profile and wallet pages (@user URL-encoded as %40user) still resolve via the new .replace(/%40/g, ...)
    • Twitter share popover URL on a post selection works
    • Article importer still extracts images from Medium/Substack-style noscript blocks

Summary by CodeRabbit

  • Bug Fixes

    • Fixed URL decoding to handle multiple encoded @ symbols across routes and pages
    • Improved HTML sanitization and idempotent tag-stripping to prevent malformed meta descriptions and content leakage
    • Enhanced lazy-image import handling for safer external content
  • Security Improvements

    • Broadened link-scheme validation and tightened media URL checks
    • Added prototype-pollution guards and stricter embed/link hostname validation
    • Strengthened username validation and switched to cryptographically secure randomness where needed

Review Change Stack

feruzm added 3 commits May 20, 2026 17:17
Bundle of fixes for high-severity static analysis findings:

- communities/create: replace Math.random()-derived `random()` with
  crypto.getRandomValues for the community owner WIF seed (#136 — was 32
  bits of entropy from a non-cryptographic PRNG)
- api/internal/seo/{sitemap-generate,blacklist-refresh}: stop returning
  raw error messages in 500 bodies; log server-side instead (#96, #97)
- post-renderer twitter enhancer + extension: switch substring matching
  on https://twitter.com to hostname-equality checks via URL parsing
  (#98, #99, #101, #102)
- render-helper:
  - sanitize-html: drop redundant javascript: blocklist alongside the
    positive http(s) whitelist; add /i to srcset scheme check
    (#103-114)
  - a.method: tighten dangerous-scheme strip to cover data:/vbscript:/file:
    in addition to javascript: (#112)
  - linkify: replace .replace('>', '') with anchored /^>/ (#95)
  - consts/regexes: escape unescaped `.` in hostname regexes for
    youtube/twitch/loom/rumble/bitchute/soundcloud/vimm (#122)
- bulk: convert `.replace("%40", X)` to `/%40/g` form across profile,
  entry, wallet, publish, waves, perks pages (~59 incomplete-sanitization
  alerts)
- tiptap-editor hive-post-extension: rewrite ReDoS-prone regex with
  per-segment `[^@\s/]+` so partition is unambiguous and matching is
  linear (#16)
- mattermost server + channels posts route: stop interpolating
  user-provided values into the console.error format string (#24, #25)
- publish-editor toolbar-fragments: add /i flag to HTML-tag regex so
  it matches uppercase <SCRIPT> (#26)
- get-pure-post-text + publish-validate-post + self-hosted blog-post:
  loop HTML-tag strip until idempotent so payloads like
  `<scr<script>ipt>` can't leak a fragment past a single-pass strip
  (#27-30)
- packages/render-helper/dist: rebuilt artifacts pick up source-level
  fixes (closes #91-95, #103-114, #116-121)

Out of scope (follow-up):
- #23 SSRF in api/import/route.ts: already mitigated by DNS-resolution
  IP allowlist + per-hop redirect validation + size/timeout caps;
  residual TOCTOU window is a documented trade-off (lines 109-114).
  Tightening requires pinning fetch() to the resolved IP via a custom
  undici dispatcher — separate PR.
- 15× `actions/missing-workflow-permissions`: workflow files need
  explicit `permissions:` blocks — separate PR.
- selection-popover: encodeURIComponent the tweet-intent URL params so
  selected post text can't break the share URL (#17)
- api/import/route.ts: strip script/style/iframe/event-handlers before
  setInnerHTML on JSDOM-parsed noscript content; validate lazy/srcset URLs
  against an http(s)/relative whitelist before promoting to <img src>
  (#18, #19) — JSDOM doesn't execute scripts, but pre-strip is
  defense-in-depth
- hive-operation-extension: validate the decoded Hive `to` field as a
  Hive username before rendering it into avatar URLs (#20)
- video-upload-threespeak #21: NOT modified — `<source src>` from
  URL.createObjectURL is a same-origin blob, false positive
- wave-follows-card + communities/create: source Math.random with
  crypto.getRandomValues for leaderboard-pick and username-suffix —
  closes downstream taint alerts on community-list-item and
  wave-follows-card sinks (#131-135)
- self-hosted floating-menu/utils: reject path segments containing
  __proto__/constructor/prototype before recursively writing nested
  config — closes prototype-pollution-utility (#22)
CodeQL's hostname-regex analysis traced the proxyBase string literal
(`https://i.ecency.com`) through setProxyBase into a `new RegExp(`^${escapedBase}/p/...`)`
call in buildSrcSet. The escape via .replace(/[.*+?^${}()|[\]\\]/g,...)
at the use site is correct, but CodeQL was flagging every spec call to
setProxyBase plus the source-file literal.

Replace the regex match with plain string ops:

  const proxyPrefix = `${proxyBase}/p/`;
  if (url.startsWith(proxyPrefix)) { … }

Functionally equivalent (the regex only checked startsWith + extracted
the segment before the query string, both trivial to do with slice/
indexOf). Spec suite stays green (19 tests in proxify-image-src.spec.ts,
all 1072 across render-helper). Dist rebuilt.
Comment thread apps/web/src/app/api/import/route.ts Fixed
Comment thread apps/web/src/app/api/import/route.ts Fixed
Comment thread apps/web/src/utils/get-pure-post-text.ts Fixed
Comment thread apps/web/src/utils/get-pure-post-text.ts Fixed
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f06b82962a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/web/src/app/api/import/route.ts Outdated
// Only http(s) and protocol-/path-relative URLs are accepted as image
// src values. Blocks javascript:/data:/vbscript: schemes that could
// otherwise be smuggled through lazy-load attributes on adversarial pages.
const SAFE_IMG_SRC_RE = /^(https?:\/\/|\/\/|\/[^/])/i;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Accept document-relative image URLs in lazy-image recovery

SAFE_IMG_SRC_RE now rejects valid document-relative URLs such as "./img.jpg", "../img.jpg", and "img.jpg". In fixLazyImages, this means data-src/srcset candidates in those forms are skipped whenever src is missing or a data placeholder, so imported articles from sites that lazy-load with relative paths will lose images even though JSDOM/Readability can resolve them against the page URL.

Useful? React with 👍 / 👎.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 20, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8933f622-420a-4ac1-8fc4-3564cba4df08

📥 Commits

Reviewing files that changed from the base of the PR and between f06b829 and 8dbc081.

⛔ Files ignored due to path filters (6)
  • packages/render-helper/dist/browser/index.js is excluded by !**/dist/**
  • packages/render-helper/dist/browser/index.js.map is excluded by !**/dist/**, !**/*.map
  • packages/render-helper/dist/node/index.cjs is excluded by !**/dist/**
  • packages/render-helper/dist/node/index.cjs.map is excluded by !**/dist/**, !**/*.map
  • packages/render-helper/dist/node/index.mjs is excluded by !**/dist/**
  • packages/render-helper/dist/node/index.mjs.map is excluded by !**/dist/**, !**/*.map
📒 Files selected for processing (13)
  • apps/self-hosted/src/features/blog/components/blog-post-page.tsx
  • apps/web/src/app/(dynamicPages)/profile/[username]/wallet/(token)/[token]/_components/hive-engine-token-history.tsx
  • apps/web/src/app/(dynamicPages)/profile/[username]/wallet/(token)/_components/profile-wallet-token-actions.tsx
  • apps/web/src/app/(dynamicPages)/profile/[username]/wallet/(token)/_components/profile-wallet-token-summary.tsx
  • apps/web/src/app/api/import/route.ts
  • apps/web/src/app/perks/promote-post/_components/promote-post-setup.tsx
  • apps/web/src/app/publish/_components/publish-validate-post.tsx
  • apps/web/src/features/post-renderer/components/extensions/twitter-extension.tsx
  • apps/web/src/features/post-renderer/components/utils/twitterEnhancer.tsx
  • apps/web/src/utils/get-pure-post-text.ts
  • packages/render-helper/CHANGELOG.md
  • packages/render-helper/package.json
  • packages/render-helper/src/methods/a.method.ts

📝 Walkthrough

Walkthrough

Normalize URL-encoded %40 decoding across routing and query code and apply security hardenings: idempotent HTML stripping, prototype-pollution guard, stricter href/src validation, proxied image handling, CSPRNG randomness, username validation, regex tightening, and structured error logging.

Changes

URL parameter encoding normalization and routing fixes

Layer / File(s) Summary
Entry & feed parameter decoding
apps/web/src/app/(dynamicPages)/entry/..., apps/web/src/api/queries/get-account-posts-feed-query.ts, apps/web/src/app/(dynamicPages)/feed/...
Entry metadata, feed prefetch, infinite queries, and feed metadata now decode all %40 occurrences with a global regex for author/tag normalization.
Profile routes and pages
apps/web/src/app/(dynamicPages)/profile/[username]/*
All profile pages and utilities use global %40 decoding for metadata, prefetching, redirects, and query construction.
Publish, selection, and waves
apps/web/src/app/publish/..., selection-popover/index.tsx, apps/web/src/app/waves/...
Publish/wave routes and selection popover now sanitize/encode inputs and decode %40 globally where applicable.

Security hardening across multiple vectors

Layer / File(s) Summary
Prototype pollution prevention
apps/self-hosted/src/features/floating-menu/utils.ts
Adds FORBIDDEN_KEYS and rejects nested update paths containing __proto__, constructor, or prototype.
Idempotent HTML/comment stripping
apps/self-hosted/src/features/blog/components/blog-post-page.tsx, apps/web/src/app/publish/_components/publish-validate-post.tsx, apps/web/src/utils/get-pure-post-text.ts
Meta-description and pure-text extraction now repeatedly strip comments/tags until stable to prevent fragmented/unclosed-tag bypass.
Safe lazy image unwrapping & src normalization
apps/web/src/app/api/import/route.ts
Parses <noscript> with DOMParser, whitelists attrs, normalizes lazy data-* sources to absolute http(s) before assigning, and tightens srcset candidate assignment.
Render-helper regex & sanitize-html tightening
packages/render-helper/src/consts/regexes.const.ts, packages/render-helper/src/methods/sanitize-html.method.ts
Escapes dots in provider hostnames and enforces ^https?:// checks for media attributes.
Link/href validation and Twitter embed checks
packages/render-helper/src/methods/a.method.ts, apps/web/src/features/post-renderer/.../twitter-extension.tsx, apps/web/src/features/post-renderer/.../twitterEnhancer.tsx
a() now whitelists allowed schemes and relative forms; Twitter/X links use URL parsing and exact hostname checks instead of prefix checks.
CSPRNG for sensitive randomness
apps/web/src/app/communities/create/_page.tsx, apps/web/src/app/waves/_components/wave-follows-card.tsx
Replace Math.random() with crypto.getRandomValues() for username/WIF generation and follower selection.
Username validation and safe rendering
apps/web/src/features/post-renderer/components/extensions/hive-operation-extension.tsx, apps/web/src/app/perks/promote-post/_components/promote-post-setup.tsx
Adds Hive username regex validation for transfer rendering; promote-post inputs and Twitter share text are more strictly encoded/sanitized.
Structured error logging & response sanitization
apps/web/src/app/api/internal/seo/*, apps/web/src/app/api/mattermost/..., apps/web/src/server/mattermost.ts
Errors are logged with structured metadata and API 500 responses omit internal error messages.
Minor refactors
apps/web/src/app/publish/_components/publish-editor-toolbar-fragments.tsx, apps/web/src/features/tiptap-editor/extensions/hive-post-extension.tsx, packages/render-helper/src/proxify-image-src.ts
Case-insensitive HTML tag detection, tightened Hive post URL regex, and proxied-srcset handling optimized via string slicing.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🐰 A careful hop through tangled tags and paths,
I strip each fragment, guard object graphs,
Encode the links, seed randomness true,
Close leaks and tighten patterns too —
The warren’s code is safer now, thanks to you!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.17% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly summarizes the main objective: closing 115 of 136 CodeQL alerts through security fixes, which is the primary focus of all changes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch security/codeql-fixes

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (1)
apps/web/src/app/perks/promote-post/_components/promote-post-setup.tsx (1)

74-74: ⚡ Quick win

Consider making the @ removal global for consistency.

The first .replace("@", "") only removes the first occurrence of @, while the second .replace(/%40/g, "") removes all occurrences of %40. If a user types multiple @ symbols (e.g., @@username), only the first would be removed. Making both replacements global would be more consistent and handle edge cases better.

♻️ Proposed refinement
-              onChange={(e) => setPathQuery(e.target.value.replace("@", "").replace(/%40/g, ""))}
+              onChange={(e) => setPathQuery(e.target.value.replace(/@/g, "").replace(/%40/g, ""))}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/app/perks/promote-post/_components/promote-post-setup.tsx` at
line 74, The onChange handler for setPathQuery only removes the first "@"
because it uses .replace("@", "") while it removes all "%40" with a global
regex; update the handler in promote-post-setup.tsx to remove all "@"
occurrences by using a global regex (e.g., replace with /@/g) before also
removing all "%40" (keep /%40/g), ensuring
setPathQuery(e.target.value.replace(/@/g, "").replace(/%40/g, "")) handles
multiple "@" reliably.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@apps/web/src/app/`(dynamicPages)/profile/[username]/wallet/(token)/_components/profile-wallet-token-actions.tsx:
- Line 44: The code currently force-casts username to string for cleanUsername
which can throw if useParams() returned undefined; replace the unsafe assertion
by guarding the params value first (check that username is a non-empty string
via typeof/boolean check or params?.username) before calling .replace(), and
either provide a safe fallback (e.g. empty string) or return early from the
component when username is missing; update the references to cleanUsername so
they rely on the guarded value (symbols: useParams, cleanUsername in
profile-wallet-token-actions.tsx) to avoid runtime errors in downstream queries
and comparisons.

In
`@apps/web/src/app/`(dynamicPages)/profile/[username]/wallet/(token)/_components/profile-wallet-token-summary.tsx:
- Line 42: The code uses (username as string).replace(...) without runtime
checks which will throw if username is undefined; change to guard and normalize
username before calling replace (e.g., derive a rawUsername via typeof username
=== "string" ? username : "" or username ?? ""), then compute cleanUsername from
that normalized string, and use cleanUsername in the query hooks (the places
using cleanUsername and the original username value) so replace is never invoked
on undefined; update any other occurrences (e.g., where cleanUsername is
referenced) to use this safely normalized variable.

In
`@apps/web/src/app/`(dynamicPages)/profile/[username]/wallet/(token)/[token]/_components/hive-engine-token-history.tsx:
- Line 26: The code uses a blind cast for the route param when creating
cleanUsername: replace the unsafe assertion by guarding the username parameter
(the username variable used to compute cleanUsername in
hive-engine-token-history.tsx) — check typeof username === "string" and either
early-return null/placeholder or handle the undefined case before computing
cleanUsername, then call username.replace(/%40/g, ""); update any callers/JSX to
expect the early return if you choose to return null.

In `@apps/web/src/app/api/import/route.ts`:
- Around line 273-274: SAFE_IMG_SRC_RE currently permits protocol-relative
("//...") and root-relative ("/...") URLs but sanitizeHtml() later only accepts
http(s)://, causing recovered images to be dropped; update the import flow to
normalize candidate image URLs to absolute http(s) before assignment: when you
detect a match via SAFE_IMG_SRC_RE (and at the other spots around 309-315,
328-330), resolve the URL against document.baseURI (or use new URL(candidate,
document.baseURI).href) and then validate that the resolved value starts with
"http://" or "https://"; assign that resolved absolute URL as the img src (or
alternatively narrow SAFE_IMG_SRC_RE to only allow ^https?:\/\/), and ensure
sanitizeHtml() sees the normalized http(s) URL so images are preserved.

In
`@apps/web/src/features/post-renderer/components/extensions/twitter-extension.tsx`:
- Around line 28-37: The hostname check for Twitter/X embeds rejects common www
prefixed links; update the validation where the URL is parsed (the URL(href)
block that returns u.protocol === "https:" && (u.hostname === "x.com" ||
u.hostname === "twitter.com")) to also accept "www.twitter.com" and "www.x.com"
(i.e., include u.hostname === "www.twitter.com" and u.hostname === "www.x.com")
so https://www.twitter.com/... and https://www.x.com/... are treated as valid
embeds.

In `@apps/web/src/features/post-renderer/components/utils/twitterEnhancer.tsx`:
- Around line 19-23: The hostname check in twitterEnhancer.tsx currently only
allows "x.com" and "twitter.com" and falsely rejects common "www." variants;
update the logic that inspects the URL (the const url = new URL(href) block and
the subsequent hostname checks) to also accept "www.twitter.com" and "www.x.com"
in addition to the existing hostnames so https://www.twitter.com/... and
https://www.x.com/... are treated as valid Twitter/X URLs; ensure the protocol
check (url.protocol !== "https:") remains, and adjust the conditional that
references url.hostname to include the two "www." variants.

---

Nitpick comments:
In `@apps/web/src/app/perks/promote-post/_components/promote-post-setup.tsx`:
- Line 74: The onChange handler for setPathQuery only removes the first "@"
because it uses .replace("@", "") while it removes all "%40" with a global
regex; update the handler in promote-post-setup.tsx to remove all "@"
occurrences by using a global regex (e.g., replace with /@/g) before also
removing all "%40" (keep /%40/g), ensuring
setPathQuery(e.target.value.replace(/@/g, "").replace(/%40/g, "")) handles
multiple "@" reliably.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1e627d0c-2e91-421f-9b79-fdff173d81f1

📥 Commits

Reviewing files that changed from the base of the PR and between 04a37b8 and f06b829.

⛔ Files ignored due to path filters (6)
  • packages/render-helper/dist/browser/index.js is excluded by !**/dist/**
  • packages/render-helper/dist/browser/index.js.map is excluded by !**/dist/**, !**/*.map
  • packages/render-helper/dist/node/index.cjs is excluded by !**/dist/**
  • packages/render-helper/dist/node/index.cjs.map is excluded by !**/dist/**, !**/*.map
  • packages/render-helper/dist/node/index.mjs is excluded by !**/dist/**
  • packages/render-helper/dist/node/index.mjs.map is excluded by !**/dist/**, !**/*.map
📒 Files selected for processing (61)
  • apps/self-hosted/src/features/blog/components/blog-post-page.tsx
  • apps/self-hosted/src/features/floating-menu/utils.ts
  • apps/web/src/api/queries/get-account-posts-feed-query.ts
  • apps/web/src/app/(dynamicPages)/entry/[category]/[author]/(layoutless)/[permlink]/edit-classic/page.tsx
  • apps/web/src/app/(dynamicPages)/entry/[category]/[author]/[permlink]/_components/selection-popover/index.tsx
  • apps/web/src/app/(dynamicPages)/entry/[category]/[author]/[permlink]/page.tsx
  • apps/web/src/app/(dynamicPages)/entry/[category]/[author]/[permlink]/redditbot/route.ts
  • apps/web/src/app/(dynamicPages)/entry/_helpers/generate-entry-metadata.ts
  • apps/web/src/app/(dynamicPages)/feed/[...sections]/_helpers/generate-feed-metadata.ts
  • apps/web/src/app/(dynamicPages)/profile/[username]/[section]/page.tsx
  • apps/web/src/app/(dynamicPages)/profile/[username]/communities/_page.tsx
  • apps/web/src/app/(dynamicPages)/profile/[username]/communities/page.tsx
  • apps/web/src/app/(dynamicPages)/profile/[username]/insights/page.tsx
  • apps/web/src/app/(dynamicPages)/profile/[username]/layout.tsx
  • apps/web/src/app/(dynamicPages)/profile/[username]/page.tsx
  • apps/web/src/app/(dynamicPages)/profile/[username]/permissions/_components/profile-permissions.tsx
  • apps/web/src/app/(dynamicPages)/profile/[username]/permissions/page.tsx
  • apps/web/src/app/(dynamicPages)/profile/[username]/referrals/page.tsx
  • apps/web/src/app/(dynamicPages)/profile/[username]/rss.xml/route.tsx
  • apps/web/src/app/(dynamicPages)/profile/[username]/rss/route.tsx
  • apps/web/src/app/(dynamicPages)/profile/[username]/settings/page.tsx
  • apps/web/src/app/(dynamicPages)/profile/[username]/trail/page.tsx
  • apps/web/src/app/(dynamicPages)/profile/[username]/wallet/(token)/[token]/_components/hive-engine-claim-rewards-button.tsx
  • apps/web/src/app/(dynamicPages)/profile/[username]/wallet/(token)/[token]/_components/hive-engine-token-history.tsx
  • apps/web/src/app/(dynamicPages)/profile/[username]/wallet/(token)/[token]/page.tsx
  • apps/web/src/app/(dynamicPages)/profile/[username]/wallet/(token)/_components/profile-wallet-token-actions.tsx
  • apps/web/src/app/(dynamicPages)/profile/[username]/wallet/(token)/_components/profile-wallet-token-summary.tsx
  • apps/web/src/app/(dynamicPages)/profile/[username]/wallet/(token)/hbd/page.tsx
  • apps/web/src/app/(dynamicPages)/profile/[username]/wallet/(token)/hive/page.tsx
  • apps/web/src/app/(dynamicPages)/profile/[username]/wallet/(token)/hp/page.tsx
  • apps/web/src/app/(dynamicPages)/profile/[username]/wallet/(token)/points/_page.tsx
  • apps/web/src/app/(dynamicPages)/profile/[username]/wallet/(token)/points/page.tsx
  • apps/web/src/app/(dynamicPages)/profile/[username]/wallet/_components/profile-wallet-collect-all-button.tsx
  • apps/web/src/app/(dynamicPages)/profile/[username]/wallet/_components/profile-wallet-external-banner.tsx
  • apps/web/src/app/(dynamicPages)/profile/[username]/wallet/_components/profile-wallet-pending-earnings.tsx
  • apps/web/src/app/(dynamicPages)/profile/[username]/wallet/_components/profile-wallet-summary.tsx
  • apps/web/src/app/(dynamicPages)/profile/[username]/wallet/page.tsx
  • apps/web/src/app/api/import/route.ts
  • apps/web/src/app/api/internal/seo/blacklist-refresh/route.ts
  • apps/web/src/app/api/internal/seo/sitemap-generate/route.ts
  • apps/web/src/app/api/mattermost/channels/[channelId]/posts/route.ts
  • apps/web/src/app/communities/create/_page.tsx
  • apps/web/src/app/perks/promote-post/_components/promote-post-setup.tsx
  • apps/web/src/app/publish/_components/publish-editor-toolbar-fragments.tsx
  • apps/web/src/app/publish/_components/publish-validate-post.tsx
  • apps/web/src/app/publish/entry/[author]/[permlink]/_components/publish-entry-action-bar.tsx
  • apps/web/src/app/publish/entry/[author]/[permlink]/_components/publish-entry-success-state.tsx
  • apps/web/src/app/publish/entry/[author]/[permlink]/_page.tsx
  • apps/web/src/app/waves/[author]/[permlink]/page.tsx
  • apps/web/src/app/waves/_components/wave-follows-card.tsx
  • apps/web/src/features/post-renderer/components/extensions/hive-operation-extension.tsx
  • apps/web/src/features/post-renderer/components/extensions/twitter-extension.tsx
  • apps/web/src/features/post-renderer/components/utils/twitterEnhancer.tsx
  • apps/web/src/features/tiptap-editor/extensions/hive-post-extension.tsx
  • apps/web/src/server/mattermost.ts
  • apps/web/src/utils/get-pure-post-text.ts
  • packages/render-helper/src/consts/regexes.const.ts
  • packages/render-helper/src/methods/a.method.ts
  • packages/render-helper/src/methods/linkify.method.ts
  • packages/render-helper/src/methods/sanitize-html.method.ts
  • packages/render-helper/src/proxify-image-src.ts

Comment thread apps/web/src/app/api/import/route.ts Outdated
Comment thread apps/web/src/features/post-renderer/components/utils/twitterEnhancer.tsx Outdated
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 20, 2026

Greptile Summary

This PR batch-closes 115 of 136 open CodeQL alerts across the develop branch, touching 69 files. The fixes span CSPRNG upgrades, URL-scheme whitelisting, regex escaping, tainted-format-string cleanup, prototype-pollution guards, and idempotent HTML-tag stripping.

  • Cryptographic randomness (generateWif, generateUsername, wave-follows-card): replaced Math.random()-derived values with crypto.getRandomValues; generateWif now draws a full 256-bit seed directly, while UI helpers use buf[0] % n (negligible modular bias for display use).
  • Scheme whitelist (a.method.ts): replaced a javascript:-only blocklist with a positive isSafeScheme/isRelative check that also rejects data:, vbscript:, file:, ms-its:, etc.
  • Noscript image recovery (api/import/route.ts): replaced innerHTML = content with a DOMParser parse followed by attribute-whitelisted <img> migration and normalizeImgSrc URL validation against an http(s) guard.

Confidence Score: 5/5

Safe to merge; all 115 targeted CodeQL alerts are addressed with well-reasoned fixes and no regressions on the 1,072-test render-helper suite.

The cryptographic key generation (generateWif) is the highest-stakes change and is correctly implemented using a full 256-bit CSPRNG seed. The scheme whitelist in a.method.ts is a strict improvement over the previous javascript:-only blocklist. The noscript image recovery replaces an innerHTML sink with an attribute-whitelist copy, and URL validation is correctly applied via normalizeImgSrc. No changes introduce wrong data, broken state, or exploitable paths; the two observations flagged are minor precision gaps with no reachable exploits on real Hive blockchain data.

apps/web/src/app/api/import/route.ts and apps/web/src/features/post-renderer/components/extensions/hive-operation-extension.tsx have the minor gaps noted in inline comments, but neither presents a real blocker.

Important Files Changed

Filename Overview
packages/render-helper/src/methods/a.method.ts Upgraded from a javascript:-only blocklist to a positive scheme whitelist + relative-URL pattern; control-character stripping prevents bypass attempts.
apps/web/src/app/api/import/route.ts Noscript image recovery rewritten to use DOMParser + attribute whitelist; normalizeImgSrc validates all resolved URLs against http(s). DOMParser availability is guarded with a conditional so noscript processing silently skips if unavailable in certain JSDOM configurations.
apps/web/src/features/post-renderer/components/extensions/hive-operation-extension.tsx Adds HIVE_USERNAME_RE guard before using the to field in avatar URL and display; regex is mostly correct but permits trailing dots and consecutive dots that aren't valid Hive usernames.
apps/web/src/app/communities/create/_page.tsx generateWif now uses 32 bytes from crypto.getRandomValues (256-bit CSPRNG seed); generateUsername uses secureRandomInt helper — solid improvement.
apps/self-hosted/src/features/floating-menu/utils.ts FORBIDDEN_KEYS set correctly blocks proto/constructor/prototype before recursive path writes; prototype pollution is fully mitigated.
packages/render-helper/src/methods/sanitize-html.method.ts Redundant javascript:-starts-with blocklist removed; positive http(s)-only whitelist now handles all scheme rejection for img src and video src/poster.
apps/web/src/features/tiptap-editor/extensions/hive-post-extension.tsx Added [^/] to path-segment character class to prevent exponential backtracking on pathological inputs.
packages/render-helper/src/consts/regexes.const.ts Escapes literal dots in all hostname regexes (YouTube, Bitchute, Twitch, Rumble, Vimm, SoundCloud, Loom, etc.) to prevent wildcard host matching.
apps/web/src/app/waves/_components/wave-follows-card.tsx Replaced Math.random() with crypto.getRandomValues for leaderboard pick; logic and loop conditions are unchanged from pre-PR.
apps/web/src/features/post-renderer/components/utils/twitterEnhancer.tsx Switched from startsWith prefix check to new URL().hostname equality for twitter.com/x.com detection, closing the twitter.com.evil.com bypass.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["Hive post body / external article URL"] --> B["render-helper pipeline"]
    B --> C{"a.method.ts\nisSafeScheme?\nisRelative?"}
    C -->|"yes"| D["href kept"]
    C -->|"no"| E["href stripped"]

    A --> F["api/import/route.ts\nfetchExternalArticle"]
    F --> G["JSDOM parse\n(url option set)"]
    G --> H{"DOMParser\navailable?"}
    H -->|"yes"| I["noscript → DOMParser\nattr-whitelist copy\nnormalizeImgSrc validate"]
    H -->|"no"| J["noscript skipped\n(silent)"]
    I --> K["Readability extract"]
    J --> K

    L["HiveOperationRenderer\nop prop (base64 JSON)"] --> M["atob + JSON.parse"]
    M --> N{"decodedOp[0]\n=== transfer?"}
    N -->|"yes"| O{"HIVE_USERNAME_RE\ntest(to)?"}
    O -->|"pass"| P["avatar URL + display"]
    O -->|"fail"| Q["transfer block hidden"]
    N -->|"no"| R["no transfer UI"]

    S["generateWif\ncommunities/create"] --> T["crypto.getRandomValues\n32-byte seed"]
    T --> U["sha256 → base58 → WIF"]
Loading

Fix All in Claude Code

Reviews (3): Last reviewed commit: "chore: apply changeset versioning for PR..." | Re-trigger Greptile

Comment thread apps/web/src/app/api/import/route.ts Outdated
Comment thread packages/render-helper/src/methods/a.method.ts Outdated
Round 2 of fixes responding to automated PR review feedback.

CodeQL (new alerts triggered by round 1):
- get-pure-post-text + publish-validate-post + blog-post-page: after
  loop tag strip, also strip residual `<`/`>` so unclosed/truncated
  tags (e.g. ending `…<script` with no closing `>`) can't leave a
  `<script` or `<!--` substring in the output. Closes #141, #142.
- api/import noscript path: replaced wrapper.innerHTML + regex
  strip with DOMParser.parseFromString (no script execution) +
  whitelisted-attribute migration of <img> elements only. Eliminates
  the innerHTML sink entirely and removes the need for the regex
  event-handler strip (which greptile flagged for missing
  /onerror-style slash-separated attributes). Closes #139, #140.

Codex P2 + coderabbit #4: SAFE_IMG_SRC_RE accepted protocol-relative
and root-relative URLs but rejected document-relative ones, and
sanitize-html downstream only keeps http(s)://. Replaced with
normalizeImgSrc(candidate, document.baseURI) that resolves every form
(absolute, //protocol, /root, ./doc, plain.jpg) against the article's
base URL and requires the absolute result be http(s). Recovered lazy
images now survive the render-helper pass.

Greptile #2: a.method href check inverted from blocklist
(javascript:/data:/vbscript:/file:) to positive whitelist
(https?/mailto/hive/tel/web+ext + relative forms). Less-common
schemes (ms-its:, mk:, res:, intent:, etc.) now also rejected.
Control-char strip widened to \f \v \0 in addition to \t \n \r.

CodeRabbit:
- twitter-extension + twitterEnhancer: also accept www.twitter.com
  and www.x.com (commonly shared with the www prefix).
- profile-wallet-token-actions, profile-wallet-token-summary (×2),
  hive-engine-token-history: replaced (username as string) force-cast
  with typeof guard so an undefined param returns "" instead of
  throwing.
- promote-post-setup: `.replace("@", "")` → `.replace(/@/g, "")` for
  consistency with the adjacent /%40/g.

Skipped:
- coderabbit autofix bot — not used.
- video-upload-threespeak #21 (URL.createObjectURL blob) — same false
  positive call as before, dismiss in CodeQL UI.

Tests: render-helper 1,072/1,072 green (covers the new a.method
whitelist via 112 a.method.spec tests). Typecheck shows no new
errors on touched files; the pre-existing TS errors in
promote-post-setup, publish-validate-post and token-picker are
unrelated and were present before this PR.
Comment thread apps/web/src/utils/get-pure-post-text.ts Fixed
Comment thread apps/web/src/utils/get-pure-post-text.ts Fixed
Round 3 — addresses the only signals from bots that re-reviewed the
round-2 commit:

CodeQL re-scan (#143, #144) — get-pure-post-text.ts:
  The `<[^>]+>` and `<!--…-->` patterns were judged incomplete because
  they don't match unclosed forms (an input ending mid-tag like
  `…<script` survives). My round-2 `replace(/[<>]/g, "")` cleanup
  satisfied correctness but the CodeQL rule judges each regex locally
  and doesn't trace forward. Switch to `(?:>|$)` / `(?:-->|$)`
  alternations so the regex itself fully strips unclosed tags on the
  same pass. Same fix applied to publish-validate-post and
  self-hosted blog-post-page for consistency.

Greptile round-2 (5/5 confidence) — a.method.ts:
  My `isRelative` regex required `\/[^/]` (at least one non-`/` after
  the slash), so a bare `href="/"` (root link to the site root) failed
  both isSafeScheme and isRelative and had its href stripped. Loosened
  to `\/[^/]?` so bare `/` is accepted alongside `/path…`.

Render-helper test suite: 1,072 / 1,072 passing.
@feruzm feruzm added the patch Bug fixes and patches (1.0.0 → 1.0.1) label May 20, 2026
@feruzm feruzm merged commit 3b55437 into develop May 20, 2026
2 of 3 checks passed
@feruzm feruzm deleted the security/codeql-fixes branch May 20, 2026 18:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

patch Bug fixes and patches (1.0.0 → 1.0.1)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants