Skip to content

πŸ”§ update: update dependencies and add Pexels background image support - #36

Open
warengonzaga wants to merge 7 commits into
mainfrom
dev
Open

πŸ”§ update: update dependencies and add Pexels background image support#36
warengonzaga wants to merge 7 commits into
mainfrom
dev

Conversation

@warengonzaga

@warengonzaga warengonzaga commented Jun 9, 2026

Copy link
Copy Markdown
Owner

This pull request introduces a major new feature: support for custom background images in banners, including a built-in Pexels image search integration in the UI. Users can now specify any HTTPS image URL as a banner background, and optionally search for images directly from Pexels within the app. The changes span backend, frontend, documentation, and configuration to enable this functionality.

New background image support:

  • Added support for a new bgimg query parameter to allow any HTTPS image URL as the banner background. The image is fetched server-side, embedded as a base64 data URI in the SVG, and scaled to cover the banner area. Fallbacks and validation are included, with a maximum image size of 10 MB. (src/banner/svg-template.ts, src/banner/types.ts, src/routes/banner.ts, src/ui/index.html) [1] [2] [3] [4] [5] [6] [7] [8] [9]

  • Updated the UI to include a new input for background image URLs, with clear priority over color/gradient backgrounds. (src/ui/index.html) [1] [2] [3] [4]

Pexels integration:

  • Added a new /api/pexels/search API route that proxies search requests to the Pexels API, enabling in-app search for landscape images. The API is enabled via a new PEXELS_API_KEY environment variable. (src/routes/pexels.ts, src/index.ts, .env.example) [1] [2] [3] [4]

  • Enhanced the UI to provide a Pexels image search widget, allowing users to search, preview, and select images with a single click. Includes attribution and clear/reset functionality. (src/ui/index.html) [1] [2]

Documentation updates:

  • Updated README.md to document the new bgimg parameter, explain how to use custom background images, and describe the Pexels integration and its configuration. (README.md) [1] [2] [3] [4]

Dependency updates:

  • Upgraded several dependencies, including @hono/node-server, @types/node, and typescript, to their latest major versions. (package.json)

Open in Devin Review

dependabot Bot and others added 4 commits June 6, 2026 19:26
* β˜• chore: Bump @types/node from 22.19.19 to 25.9.1

Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 22.19.19 to 25.9.1.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 25.9.1
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* β˜• chore: update bun lockfile

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Waren Gonzaga <opensource@warengonzaga.com>
* β˜• chore: Bump @hono/node-server from 1.19.14 to 2.0.4

Bumps [@hono/node-server](https://github.com/honojs/node-server) from 1.19.14 to 2.0.4.
- [Release notes](https://github.com/honojs/node-server/releases)
- [Commits](honojs/node-server@v1.19.14...v2.0.4)

---
updated-dependencies:
- dependency-name: "@hono/node-server"
  dependency-version: 2.0.4
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* β˜• chore: update bun lockfile

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Waren Gonzaga <opensource@warengonzaga.com>
* β˜• chore: Bump typescript from 5.9.3 to 6.0.3

Bumps [typescript](https://github.com/microsoft/TypeScript) from 5.9.3 to 6.0.3.
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Commits](microsoft/TypeScript@v5.9.3...v6.0.3)

---
updated-dependencies:
- dependency-name: typescript
  dependency-version: 6.0.3
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* β˜• chore: update bun lockfile

* βš™οΈ setup: add ignoreDeprecations for typescript 6 compatibility

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Waren Gonzaga <opensource@warengonzaga.com>
* πŸ“¦ new: add custom background image support with Pexels integration

* πŸ”§ update: fix biome formatting for contentType assignment

* πŸ”’ security: harden image fetch and fix review findings

* πŸ”’ security: block redirects, reject non-image responses, fix fallback

* πŸ“– docs: document PEXELS_API_KEY in .env.example

* πŸ”’ security: block localhost hostnames and fix IPv6 prefix false positives

---------

Co-authored-by: Waren Gonzaga <opensource@warengonzaga.com>
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 9, 2026 01:17
@warengonzaga warengonzaga self-assigned this Jun 9, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR adds support for using remote background images in generated banners (via a new bgimg query param), plus an in-app Pexels search UI backed by a new proxy API route, alongside a set of dependency upgrades.

Changes:

  • Add bgimg background-image support (server-side fetch + base64 embed in SVG) with URL validation/SSRF mitigations.
  • Add /api/pexels/search proxy endpoint and UI widget to search/select Pexels images.
  • Upgrade core build/runtime dependencies (TypeScript, Node types, @hono/node-server, etc.) and update docs/config.

Reviewed changes

Copilot reviewed 10 out of 12 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
tsconfig.json Adds TS ignoreDeprecations setting for TS 6.x.
src/utils/sanitize.ts Adds isValidImageUrl and private-host checks for bgimg.
src/ui/index.html Adds background image input and Pexels search widget + query param wiring.
src/routes/pexels.ts New Pexels proxy search API route.
src/routes/banner.ts Adds bgimg query param parsing and background selection logic.
src/index.ts Registers the new Pexels route.
src/banner/types.ts Extends BackgroundPreset to support image backgrounds.
src/banner/svg-template.ts Implements server-side image fetch + base64 embedding for SVG backgrounds.
README.md Documents bgimg and Pexels integration.
package.json Upgrades dependencies/devDependencies.
bun.lock Lockfile updates for dependency bumps.
.env.example Adds PEXELS_API_KEY configuration.

Comment thread src/banner/svg-template.ts Outdated
Comment thread src/banner/svg-template.ts Outdated
Comment thread src/utils/sanitize.ts
Comment thread src/routes/pexels.ts
Comment thread .env.example Outdated
Comment thread package.json
Comment on lines 21 to 27
"devDependencies": {
"@biomejs/biome": "^2.4.7",
"@types/node": "^22.10.0",
"@types/node": "^25.9.1",
"tsup": "^8.3.0",
"tsx": "^4.19.0",
"typescript": "^5.7.0"
"typescript": "^6.0.3"
},

@warengonzaga warengonzaga left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

All reported implementation concerns have been acknowledged and remain open for code changes before resolution.

Comment thread src/banner/svg-template.ts Outdated
const contentType = ALLOWED_IMAGE_TYPES.find((t) =>
rawContentType.startsWith(t),
);
if (!contentType) return null;

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Acknowledged. Image responses should require an explicit allowlisted content type before embedding. Kept open pending the validation change.

bgRect = `<image href="${dataUri}" x="0" y="0" width="${WIDTH}" height="${HEIGHT}" preserveAspectRatio="xMidYMid slice" />`;
} else {
bgRect = `<rect width="${WIDTH}" height="${HEIGHT}" fill="#1a1a1a" />`;
}

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Acknowledged. The failure path should restore the documented default gradient. Kept open pending the fallback fix.

Comment thread src/utils/sanitize.ts
} catch {
return false;
}
}

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Acknowledged. URLs with embedded credentials should be rejected before server-side fetches. Kept open pending the validation change.

Comment thread src/routes/pexels.ts
const url = `${PEXELS_API_URL}/search?query=${encodeURIComponent(query)}&page=${page}&per_page=${perPage}&orientation=${orientation}`;
const response = await fetch(url, {
headers: { Authorization: apiKey },
});

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Acknowledged. The Pexels request needs a timeout to prevent stalled upstream calls. Kept open pending the fix.

Comment thread .env.example Outdated

# Pexels Integration (optional - enables the in-UI image search widget)
# Get a free API key at https://www.pexels.com/api/
# When unset, the /api/pexels/search endpoint returns 503 and the search is hidden

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Acknowledged. The environment documentation should match the actual interface behavior. Kept open pending the documentation or UI fix.

Comment thread package.json
"tsx": "^4.19.0",
"typescript": "^5.7.0"
"typescript": "^6.0.3"
},

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Acknowledged. Node type definitions should align with the supported runtime major. Kept open pending the dependency correction.

Copy link
Copy Markdown
Owner Author

@warengonzaga Na-push na ang dalawang verified fixes: may timeout na ang Pexels request at naitama na ang Pexels configuration note. Naiwan munang open ang iba pang code review items habang inaayos pa ang mga ito.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Devin Review found 1 potential issue.

Open in Devin Review

Comment thread src/ui/index.html
subheaderFontInput.value = 'Kinewave';
bgInput.value = '1A1A1A';
bgInput2.value = '4A4A4A';
bgImgInput.value = '';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟑 Resetting the form leaves the Pexels search results and controls on screen

Clicking Reset clears the background image field (bgImgInput.value = '' at src/ui/index.html:2425) but does not clear the Pexels search box, thumbnail results, attribution line, or the Clear button, so the widget still shows stale search results that no longer match the reset banner.
Impact: After a reset, users see leftover Pexels image results and controls that look active but no longer correspond to the banner, which is confusing.

Reset handler updates only bgImgInput, not the Pexels widget state

The Pexels clear handler at src/ui/index.html:2388-2396 shows the full set of elements that need resetting (pexelsResults.innerHTML, pexelsResults.style.display, pexelsAttribution.style.display, pexelsClearBtn.style.display, pexelsQuery.value). The reset button handler at src/ui/index.html:2417-2458 only sets bgImgInput.value = '' and never touches these Pexels elements, so any previously rendered results grid, attribution text, Clear button, and search query remain visible after a reset.

Prompt for agents
In the reset button click handler in src/ui/index.html (around line 2417-2458), the code sets bgImgInput.value = '' but does not reset the Pexels search widget. To make the reset consistent with the dedicated Pexels clear handler (around lines 2388-2396), also clear pexelsQuery.value, set pexelsResults.innerHTML = '', set pexelsResults.style.display = 'none', set pexelsAttribution.style.display = 'none', and set pexelsClearBtn.style.display = 'none'. Note these Pexels element references are declared as consts inside the same script scope, so they are accessible from the reset handler.
Open in Devin Review

Was this helpful? React with πŸ‘ or πŸ‘Ž to provide feedback.

Copy link
Copy Markdown
Owner Author

@warengonzaga Na-push at local-test na ang tatlong natitirang background-image fixes: strict image content-type validation, pag-block ng URLs na may embedded credentials, at default gradient fallback kapag hindi ma-fetch ang image. Successful ang production build at focused validation checks.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

View 3 additional findings in Devin Review.

Open in Devin Review

Comment thread src/utils/sanitize.ts
Comment on lines +169 to +180
export function isValidImageUrl(value: string): boolean {
if (!value || value.length > MAX_IMAGE_URL_LENGTH) return false;
try {
const url = new URL(value);
if (url.protocol !== 'https:') return false;
if (url.username || url.password) return false;
if (isPrivateHost(url.hostname)) return false;
return true;
} catch {
return false;
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟨 Server-side image fetch is vulnerable to SSRF via DNS rebinding

The new bgimg feature fetches an arbitrary user-supplied HTTPS URL server-side (fetchImageAsBase64 in src/banner/svg-template.ts:40-92). The URL is validated by isValidImageUrl (src/utils/sanitize.ts:169-180), which blocks private/loopback/link-local/metadata IP literals via isPrivateHost and enforces redirect: 'error'. However, validation only inspects the hostname string at request time; a public hostname that resolves to an internal IP (169.254.169.254, 10.x, etc.) at fetch time bypasses these checks (DNS rebinding). An attacker could point bgimg at an attacker-controlled domain resolving to cloud metadata or internal services, and while raw bodies are only re-embedded when they match allowed image content-types, response availability/timing and content-length can still leak internal-network information.

Open in Devin Review

Was this helpful? React with πŸ‘ or πŸ‘Ž to provide feedback.

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.

2 participants