diff --git a/.github/workflows/sanity.yml b/.github/workflows/sanity.yml index 847d0cb..8894fca 100644 --- a/.github/workflows/sanity.yml +++ b/.github/workflows/sanity.yml @@ -15,14 +15,14 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Check public-preview file set shell: bash run: | set -euo pipefail - for file in AGENTS.md CLAUDE.md CHANGELOG.md CONTRIBUTING.md LICENSE README.md SECURITY.md manifest.json package.json docs/00_goal.md docs/01_requirements.md docs/02_runbook.md docs/03_status.md docs/04_decisions.md docs/06_mv3_design.md docs/07_manual_verification.md docs/08_manual_verification_result_template.md + for file in .github/dependabot.yml AGENTS.md CLAUDE.md CHANGELOG.md CONTRIBUTING.md LICENSE README.md SECURITY.md manifest.json package.json docs/00_goal.md docs/01_requirements.md docs/02_runbook.md docs/03_status.md docs/04_decisions.md docs/06_mv3_design.md docs/07_manual_verification.md docs/08_manual_verification_result_template.md do test -f "$file" done diff --git a/CHANGELOG.md b/CHANGELOG.md index 00b1d85..50c48aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,13 @@ All notable product changes will be recorded in this file. - Added unsupported-page safeguards and exact manifest-permission tests. - Added immediate `Copied!` feedback with timed label restoration. - Added public-safe manual verification guidance. +- Restricted body and comment extraction to visible `innerText` without hidden + `textContent` fallback. +- Added page title provenance to Markdown output. +- Added structural blockquote boundaries for untrusted body and comment + previews. +- Added regression checks for hidden content, Markdown structure, background + runtime absence, and external communication primitives. This entry describes the source preview. It is not a release, tag, package, or Chrome Web Store publication record. diff --git a/README.md b/README.md index 8647ddd..b368f44 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,8 @@ a complete GitHub conversation exporter. - recognizes GitHub Issue pages - recognizes GitHub Pull Request conversation pages - extracts the visible title, source URL, body preview, and visible comment snippets -- creates a Markdown preview with explicit limitations and a review-before-sharing warning +- creates a Markdown preview with the title, explicit limitations, and a review-before-sharing warning +- keeps body and comment previews inside an explicit untrusted-content blockquote boundary - copies the Markdown to the clipboard - saves the Markdown as a local `.md` file - fails closed when metadata injection fails @@ -70,7 +71,8 @@ See [SECURITY.md](SECURITY.md) for the security policy and reporting route. defines reusable maintainer handoff and task-packet structures. GitHub Web Context Exporter is a smaller browser-side entry point that captures only the currently visible GitHub page context. It does not replace repository-aware -context gathering or generate a complete task packet. +context gathering or generate a complete task packet. The projects do not +automatically transfer files or share a runtime schema. ## Development diff --git a/SECURITY.md b/SECURITY.md index 72ddae2..11bfc8c 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -11,8 +11,8 @@ made for earlier preview revisions. ## Report a vulnerability -After the public repository enables GitHub Private Vulnerability Reporting, -use the repository's **Security** tab and select **Report a vulnerability**. +Use GitHub Private Vulnerability Reporting from the repository's **Security** +tab and select **Report a vulnerability**. Do not place credentials, private repository content, private logs, identifying screenshots, or exploit details in a public Issue. If the private reporting diff --git a/docs/01_requirements.md b/docs/01_requirements.md index 6b5cce3..c0884a4 100644 --- a/docs/01_requirements.md +++ b/docs/01_requirements.md @@ -32,6 +32,7 @@ Pull Request support is limited to the Conversation page. - page title - visible body preview or an explicit unavailable fallback - visible comment snippets or an explicit unavailable fallback +- body and comment previews structurally marked as untrusted page content The preview does not claim complete state, label, body, comment, review, diff, check, or files-changed coverage. @@ -66,3 +67,4 @@ diff, check, or files-changed coverage. - no analytics or cloud sync - no GitHub write operation - manual review required before sharing exported Markdown +- no automatic transfer to Maintainer Context Kit diff --git a/docs/02_runbook.md b/docs/02_runbook.md index 698bd5d..b1de03c 100644 --- a/docs/02_runbook.md +++ b/docs/02_runbook.md @@ -16,7 +16,8 @@ npm test The suite covers URL classification, Markdown formatting, filename generation, manifest permissions, self-contained metadata injection, fail-closed behavior, -unsupported pages, and Copy feedback. +hidden-content exclusion, untrusted Markdown framing, runtime network +boundaries, unsupported pages, and Copy feedback. ## Load unpacked diff --git a/docs/03_status.md b/docs/03_status.md index 055dda4..b346b5f 100644 --- a/docs/03_status.md +++ b/docs/03_status.md @@ -1,10 +1,10 @@ # Status -Last updated: 2026-07-16 +Last updated: 2026-07-30 ## Current phase -Maintainer-controlled OSS preview candidate. +Maintainer-controlled public OSS preview. ## Implemented @@ -12,9 +12,13 @@ Maintainer-controlled OSS preview candidate. - GitHub Issue and Pull Request Conversation URL classification - self-contained visible-page metadata injection - visible title, source URL, body preview, and visible comment snippets -- explicit selector fallbacks +- rendered-visibility checks that reject hidden, inert, transparent, or + non-rendered body/comment elements +- explicit body selectors that do not promote the first comment into a + missing body preview - fail-closed injection-error behavior - Markdown preview with limitation and review-before-sharing sections +- title provenance and blockquoted untrusted body/comment previews - Copy Markdown with immediate `Copied!` feedback and timed restoration - local Markdown save without the Chrome Downloads API - exact manifest-permission regression tests @@ -48,11 +52,30 @@ There are no host permissions. ## Verification baseline -- automated test suite: 29 tests -- public-safe Issue and Pull Request Conversation checks -- unsupported repository-root check -- Copy and Download checks -- manifest-permission check +- automated test suite: 38 tests in this local candidate +- exact local candidate loaded with `Load unpacked` in an isolated Chrome for + Testing profile on 2026-07-30 +- public-safe Issue check covering title, canonical URL, visible body, and + visible comments +- public-safe Pull Request Conversation check covering title, canonical URL, + visible body, and an explicit no-visible-comments fallback +- unsupported repository-root check with no Markdown and disabled Copy/save +- successful user-gesture Copy check, including `Copied!` feedback and timed + restoration +- successful local Markdown save for both the Issue and Pull Request +- manifest-permission check for `activeTab`, `scripting`, and `clipboardWrite`, + with no host permissions Verification results describe the tested revision only and are not a production-readiness guarantee. + +## Current product interpretation + +The extension is a short orientation snapshot, not a faithful full-conversation +export. Body and comment previews are deliberately capped. State, labels, +complete bodies, full comment history, review threads, diffs, and check results +remain outside the current scope. + +Maintainer Context Kit is a separate repository-aware workflow. There is no +automatic file import, stdin bridge, or shared runtime schema between the +projects. diff --git a/docs/04_decisions.md b/docs/04_decisions.md index cb76c98..c2142e1 100644 --- a/docs/04_decisions.md +++ b/docs/04_decisions.md @@ -58,3 +58,12 @@ export, redaction, or secret-scanner claims. Reason: the tested local workflow is useful, while broader distribution and support promises remain out of scope. + +## Independent cross-product operation + +Decision: keep this extension and Maintainer Context Kit independently +operable. Do not add file import, stdin transfer, or a shared runtime schema +without repeated workflow evidence that manual transfer is a material problem. + +Reason: the products have different trust and collection boundaries. Coupling +them now would add maintenance surface without demonstrated user demand. diff --git a/docs/06_mv3_design.md b/docs/06_mv3_design.md index 2f3fd22..4fd9d8c 100644 --- a/docs/06_mv3_design.md +++ b/docs/06_mv3_design.md @@ -27,6 +27,8 @@ The extension has no service worker or content-script registration. 6. The popup validates the injection result and fails closed if extraction did not execute successfully. 7. The formatter creates a visible-page-only Markdown preview. + Page-derived body and comment previews remain inside blockquotes marked as + untrusted content. 8. The user can copy or locally save the Markdown. ## Supported URL patterns @@ -42,6 +44,9 @@ Prefer an explicit fallback or extraction failure over a misleading export. Hidden, collapsed, paginated, dynamically unloaded, diff, check, and files-changed content is outside the completeness claim. +The extractor uses visible `innerText` for page-derived body and comment +content. It does not fall back to hidden `textContent`. + ## Rendering principle Page-derived content is rendered through `textContent` or form-control diff --git a/docs/07_manual_verification.md b/docs/07_manual_verification.md index d358d73..56a5dfa 100644 --- a/docs/07_manual_verification.md +++ b/docs/07_manual_verification.md @@ -1,6 +1,6 @@ # Manual Verification Guide -Last updated: 2026-07-16 +Last updated: 2026-07-30 This guide verifies the current local extension behavior. It is not a release or store-publishing checklist. @@ -48,17 +48,19 @@ If testing on private repository pages, do not paste, publish, screenshot, or sh 4. Confirm the Markdown preview includes: - `GitHub Issue Context` - `Source` + - the current document title and page URL under `Source` - `Limitations` - `Review Before Sharing` - `Body Preview` - `Visible Comments Preview` - `Suggested Next Use` -5. Click `Copy Markdown`. -6. Confirm the button immediately changes to `Copied!`. -7. Confirm the button returns to `Copy Markdown` after about 1.5 seconds. -8. Paste into a local scratch document and confirm Markdown was copied. -9. Click `Download Markdown`. -10. Confirm a local `.md` file is saved with a repository/page-based filename. +5. Confirm body and comment snippets are blockquoted as untrusted page content. +6. Click `Copy Markdown`. +7. Confirm the button immediately changes to `Copied!`. +8. Confirm the button returns to `Copy Markdown` after about 1.5 seconds. +9. Paste into a local scratch document and confirm Markdown was copied. +10. Click `Download Markdown`. +11. Confirm a local `.md` file is saved with a repository/page-based filename. ## Verify a Pull Request conversation page @@ -74,13 +76,15 @@ If testing on private repository pages, do not paste, publish, screenshot, or sh 4. Confirm the Markdown preview includes: - `GitHub Pull Request Context` - `Source` + - the current document title and page URL under `Source` - `Limitations` - `Review Before Sharing` - `Body Preview` - `Visible Comments Preview` - `Suggested Next Use` -5. Confirm the preview does not claim to include Files changed, diffs, checks, hidden content, or complete review threads. -6. Confirm Copy and Download behave the same as on Issue pages. +5. Confirm body and comment snippets are blockquoted as untrusted page content. +6. Confirm the preview does not claim to include Files changed, diffs, checks, hidden or collapsed content, or complete review threads. +7. Confirm Copy and Download behave the same as on Issue pages. ## Verify unsupported pages @@ -116,6 +120,10 @@ Manual verification passes when: - supported Issue pages produce a Markdown preview - supported Pull Request conversation pages produce a Markdown preview +- Source records the current document title and page URL +- body and comment snippets remain inside blockquotes as untrusted content +- hidden or collapsed DOM content is not promoted into body or comment previews +- a visible comment is not promoted into a missing body preview - unsupported pages fail safely - Copy Markdown works, shows `Copied!`, and restores its label - Download Markdown saves a local `.md` file @@ -131,4 +139,6 @@ Stop and open a follow-up issue if: - the output claims completeness that is not implemented - permissions expand unexpectedly - generated Markdown omits the review-before-sharing warning +- hidden or collapsed DOM text appears in the generated preview +- a comment is mislabeled as the page body - local save requires a new permission diff --git a/docs/08_manual_verification_result_template.md b/docs/08_manual_verification_result_template.md index 348a326..e4de725 100644 --- a/docs/08_manual_verification_result_template.md +++ b/docs/08_manual_verification_result_template.md @@ -21,7 +21,7 @@ Do not expand extraction scope until this result is filled out and any blocking Choose one: -- [ ] Pass: current behavior is acceptable for continued private iteration. +- [ ] Pass: current behavior is acceptable for the maintainer-controlled public preview. - [ ] Conditional pass: minor issues recorded below; no privacy, permission, or misleading-output blocker. - [ ] Fail: stop feature expansion and fix blockers first. @@ -45,8 +45,12 @@ Notes: - [ ] Type is `issue`. - [ ] Number is shown. - [ ] Page title is shown or clear fallback appears. +- [ ] Source includes the current document title and page URL. - [ ] Body Preview appears or clear fallback appears. - [ ] Visible Comments Preview appears or clear fallback appears. +- [ ] Body and comment snippets are blockquoted as untrusted content. +- [ ] Hidden or collapsed content is not included. +- [ ] A visible comment is not mislabeled as a missing body. - [ ] Markdown preview includes limitation language. - [ ] Markdown preview includes review-before-sharing language. @@ -62,8 +66,12 @@ Notes: - [ ] Type is `pull request`. - [ ] Number is shown. - [ ] Page title is shown or clear fallback appears. +- [ ] Source includes the current document title and page URL. - [ ] Body Preview appears or clear fallback appears. - [ ] Visible Comments Preview appears or clear fallback appears. +- [ ] Body and comment snippets are blockquoted as untrusted content. +- [ ] Hidden or collapsed content is not included. +- [ ] A visible comment is not mislabeled as a missing body. - [ ] Markdown preview does not claim Files changed, diffs, checks, or complete review threads. Notes: @@ -150,7 +158,7 @@ List issues that may be fixed later. Choose one: -- [ ] Continue private iteration. +- [ ] Continue the maintainer-controlled public preview. - [ ] Fix blockers before further development. - [ ] Re-run manual verification after fixes. diff --git a/test/runtimeBoundary.test.mjs b/test/runtimeBoundary.test.mjs new file mode 100644 index 0000000..001da2a --- /dev/null +++ b/test/runtimeBoundary.test.mjs @@ -0,0 +1,52 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { readdir, readFile } from "node:fs/promises"; +import { extname } from "node:path"; + +async function runtimeSource() { + const root = new URL("../src/", import.meta.url); + const pending = [root]; + const files = []; + + while (pending.length > 0) { + const directory = pending.pop(); + const entries = await readdir(directory, { withFileTypes: true }); + + for (const entry of entries) { + const url = new URL(entry.name, directory); + if (entry.isDirectory()) { + url.pathname += "/"; + pending.push(url); + } else if (extname(entry.name) === ".js") { + files.push(await readFile(url, "utf8")); + } + } + } + + return files.join("\n"); +} + +test("manifest keeps the no-background and no-remote-code boundary", async () => { + const manifest = JSON.parse(await readFile(new URL("../manifest.json", import.meta.url), "utf8")); + + assert.equal(manifest.background, undefined); + assert.equal(manifest.content_scripts, undefined); + assert.equal(manifest.externally_connectable, undefined); + assert.equal(manifest.update_url, undefined); +}); + +test("runtime source contains no external communication primitives", async () => { + const source = await runtimeSource(); + const forbidden = [ + /\bfetch\s*\(/, + /\bXMLHttpRequest\b/, + /\bWebSocket\b/, + /\bEventSource\b/, + /\bsendBeacon\s*\(/, + /\bconnectNative\s*\(/ + ]; + + for (const pattern of forbidden) { + assert.equal(pattern.test(source), false, `${pattern} must not appear in runtime source`); + } +});