Skip to content

Harden HTML export extension#1

Merged
zakelfassi merged 4 commits into
mainfrom
s-tier-extension-hardening
Apr 25, 2026
Merged

Harden HTML export extension#1
zakelfassi merged 4 commits into
mainfrom
s-tier-extension-hardening

Conversation

@zakelfassi
Copy link
Copy Markdown
Owner

Summary

  • harden rich HTML export validation and avoid nested standalone documents
  • keep the repo private/git-first with Pi and OMP entry metadata, focused README install guidance, PNPM tests, and minimal CI
  • make /html-last default to local export and add Pi/OMP-compatible input interception to prevent slash command fallthrough into the agent spinner path

Verification

  • pnpm test
  • node --check index.js && node --check test/extension.test.js
  • pi -e ./index.js --offline --no-tools -p "/html-last" (completed; Pi emitted only the known model-pattern warning)
  • omp -e ./index.js --offline --no-tools -p "/html-last"

Notes

  • LazyPi-specific commands remain intentionally undocumented until official docs are available.
  • Commit is SSH-signed and locally verified with an allowed signers file.

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: 833b623a76

ℹ️ 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 index.js Outdated
Comment on lines +715 to +717
child.on('error', () => {});
child.unref();
return true;
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 Wait for open command failure before reporting success

openArtifact now returns true immediately after spawn, but spawn reports missing/failed commands asynchronously via the error event. On Linux hosts without xdg-open (common in headless/server environments), this path still reports “Opened … in your default browser” even though nothing was launched, which is a user-visible false success regression from the previous awaited execFile behavior.

Useful? React with 👍 / 👎.

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.

Addressed in f5fcac7. openArtifact now resolves the launcher before spawning and waits for immediate spawn errors or quick non-zero exits before reporting the browser-open success notification. Added regression coverage for missing PATH launchers via resolveOpenCommand. Verified with pnpm test, node --check index.js && node --check test/extension.test.js, and Pi/OMP offline /html-last smoke checks.

@zakelfassi zakelfassi force-pushed the s-tier-extension-hardening branch from 833b623 to f5fcac7 Compare April 25, 2026 03:56
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: f5fcac7d51

ℹ️ 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 index.js Outdated
const forcedMode = resolveForcedExportMode(args);
let mode = forcedMode || 'local';
if (mode === 'choose') {
mode = ctx && ctx.hasUI ? await chooseCommandExportMode(ctx) : 'local';
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 Honor chooser mode when select UI is available

/html-last choose only enters the chooser path when ctx.hasUI is truthy, even though chooser support is actually determined by ctx.ui.select. In command contexts that provide ui.select but omit hasUI (which is plausible given other handlers only check ctx.ui), this branch falls back to local and immediately exports instead of presenting choices, so the new choose argument does not behave as documented. The gate here should align with chooseCommandExportMode and check for selectable UI capability rather than hasUI.

Useful? React with 👍 / 👎.

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.

Addressed in c754578. /html-last choose now gates on selectable UI capability (ctx.ui.select) rather than ctx.hasUI, so command contexts that provide ui.select without hasUI still open the chooser. Added a focused hasSelectableUi regression assertion and re-ran tests/smokes.

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: ff59d70ac9

ℹ️ 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 index.js Outdated
const BLOCKED_META_REFRESH = /<\s*meta\b[^>]*http-equiv\s*=\s*(['"]?)refresh\1/i;
const EVENT_HANDLER_ATTR = /\s+on[a-z]+\s*=/i;
const JAVASCRIPT_URL_ATTR = /\s(?:href|src|xlink:href|action|formaction)\s*=\s*(['\"]?)\s*javascript:/i;
const EXTERNAL_ASSET_ATTR = /\s(?:src|poster)\s*=\s*(['\"]?)\s*(?: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.

P1 Badge Reject external assets loaded via srcset attributes

The external-asset validator only checks src and poster, so rich output like <img src="data:..." srcset="https://example.com/a.png 1x"> passes validation and is written to disk. When the artifact is opened, the browser will still fetch that remote URL, which bypasses the new “no external assets” safety boundary and leaks network metadata from local sessions.

Useful? React with 👍 / 👎.

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.

Addressed in c754578. The rich HTML external asset check now rejects remote URLs in srcset attributes, including cases where src itself is local/data. Added a srcset="https://..." regression case under rich validation.

Comment thread index.js Outdated
const EVENT_HANDLER_ATTR = /\s+on[a-z]+\s*=/i;
const JAVASCRIPT_URL_ATTR = /\s(?:href|src|xlink:href|action|formaction)\s*=\s*(['\"]?)\s*javascript:/i;
const EXTERNAL_ASSET_ATTR = /\s(?:src|poster)\s*=\s*(['\"]?)\s*(?:https?:)?\/\//i;
const EXTERNAL_CSS_URL = /url\(\s*(['\"]?)\s*(?: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 Block remote CSS loaded through @import directives

The CSS external-resource check only looks for url(http...), so a style block using @import "https://example.com/x.css"; is currently accepted by validateRichHtmlDocument. That means rich exports can still pull third-party stylesheets at open time, contradicting the inline-only hardening and weakening the security model introduced in this commit.

Useful? React with 👍 / 👎.

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.

Addressed in c754578. The CSS external-resource check now rejects remote @import directives in addition to url(http...). Added an @import "https://..." regression case under rich validation.

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: c754578a08

ℹ️ 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 index.js Outdated
const BLOCKED_META_REFRESH = /<\s*meta\b[^>]*http-equiv\s*=\s*(['"]?)refresh\1/i;
const EVENT_HANDLER_ATTR = /\s+on[a-z]+\s*=/i;
const JAVASCRIPT_URL_ATTR = /\s(?:href|src|xlink:href|action|formaction)\s*=\s*(['\"]?)\s*javascript:/i;
const EXTERNAL_ASSET_ATTR = /\s(?:(?:src|poster)\s*=\s*(['\"]?)\s*(?:https?:)?\/\/|srcset\s*=\s*(['\"]?)[^'\">]*(?: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.

P1 Badge Reject external asset URLs in href-like SVG attrs

validateRichHtmlDocument promises to block external assets, but the EXTERNAL_ASSET_ATTR pattern only checks src, poster, and srcset. Rich HTML containing SVG asset elements like <image href="https://example.com/a.png"> or <use xlink:href="https://example.com/s.svg#id"> passes validation and is written to disk, and browsers will fetch those URLs when the artifact is opened. This bypasses the new no-external-assets safety boundary for rich exports.

Useful? React with 👍 / 👎.

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.

Agreed, this is worth addressing because SVG image/use hrefs can fetch remote assets at open time. Addressed in 8bb6989: the rich HTML asset validator now rejects remote href/xlink:href on SVG asset tags (image, use, feImage) and includes regression cases for both <image href="https://..."> and <use xlink:href="https://...">. Verified with pnpm test, syntax checks, Pi/OMP /html-last smokes, and git diff --check.

@zakelfassi zakelfassi merged commit 7b9e546 into main Apr 25, 2026
1 check passed
@zakelfassi zakelfassi deleted the s-tier-extension-hardening branch April 25, 2026 05:07
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