From 520a6d98556c0ee6a3b00698dd5fd4fafb19baca Mon Sep 17 00:00:00 2001 From: Wei Wang Date: Fri, 18 Sep 2026 16:03:01 -0400 Subject: [PATCH 1/2] Guard the public boundary in CI, not at release time MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The repository is public and had no automated check that internal material stays out of it — the grep lived in local notes and ran when someone remembered. By the time a leak is tagged it is already fetchable, and history keeps it there. Three phases, one per way material has escaped or could: - internal needles in any tracked file, via `git grep` so the set scanned is exactly the set that can reach the remote; - every package-lock `resolved` URL pointing at the public registry. The npm registry configured in this dev environment is the internal one and the public one is unreachable, so each `npm install` pulls internal hosts in and the rewrite back out is manual, hence forgettable, hence asserted. The upstream data model is a git dependency and is the one allowed exception; - local-only notes absent from the index. .gitignore does not apply to files already tracked, so one `git add -f` makes CLAUDE.md permanent while .gitignore goes on looking correct. The needle scan is case-sensitive on purpose: `ipws` is four characters and occurs inside ordinary camelCase, so `-i` flags every `skipWs()` call in rowFilter.ts. A check that cries wolf on correct code gets muted. Runs before `npm ci` — it needs no dependencies, and it is the one check whose failure cannot be fixed by retrying. --- .github/workflows/ci.yml | 6 ++ package.json | 2 + scripts/leak-check.mjs | 144 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 152 insertions(+) create mode 100644 scripts/leak-check.mjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8aeecfd..4037e64 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,6 +19,12 @@ jobs: node-version: 20 cache: npm + # Before `npm ci`, deliberately: the check needs no dependencies, and a leak + # should be reported in seconds rather than behind a five-minute build. It is + # also the one check whose failure cannot be fixed by retrying. + - name: Leak check + run: npm run check:leak + - name: Install dependencies run: npm ci diff --git a/package.json b/package.json index c270fe2..0a5e527 100644 --- a/package.json +++ b/package.json @@ -189,6 +189,8 @@ "typecheck": "tsc --noEmit", "test": "vitest run", "test:coverage": "vitest run --coverage", + "check:leak": "node scripts/leak-check.mjs", + "verify": "npm run typecheck && npm run build && npm run build:web && npm run test && npm run check:leak", "compile:test": "node esbuild.test.mjs", "test:integration": "npm run build && npm run compile:test && vscode-test", "package": "vsce package" diff --git a/scripts/leak-check.mjs b/scripts/leak-check.mjs new file mode 100644 index 0000000..d9ebb93 --- /dev/null +++ b/scripts/leak-check.mjs @@ -0,0 +1,144 @@ +// Copyright 2026 The MathWorks, Inc. +// Leak check: nothing internal to MathWorks may reach this tree. The repository is +// public, so this guards the boundary on every push rather than on release — by the +// time a leak is tagged it is already fetchable, and git history keeps it there. +// +// Three phases, each guarding a different way internal material has actually escaped +// or could: a needle in prose or code, an internal registry URL regressed into the +// lockfile, and a local-only notes file force-added past .gitignore. + +import { execFileSync } from 'node:child_process'; +import { readFileSync } from 'node:fs'; + +// `data-explorer-ts` is the internal codename for the subsystem this extension grew +// out of; the public name is "data explorer". The rest are internal infrastructure +// hostnames and the internal forge. +const NEEDLES = ['insidelabs', 'ipws', 'mw-npm-repository', 'gitlab', 'data-explorer-ts']; + +// The lockfile must resolve against the PUBLIC registry. The npm registry configured +// in the dev environment here is the internal Artifactory, and it is the public one +// that is unreachable — so every `npm install` pulls internal `resolved` URLs in and +// they have to be rewritten back out. That rewrite is manual, which means it is +// forgettable, which is why it is asserted. Content-based `integrity` hashes stay +// valid across the rewrite, so a correct lockfile is still an installable one. +const PUBLIC_REGISTRY = 'https://registry.npmjs.org/'; + +// The single legitimate exception: the upstream data model is a git dependency, not a +// registry one, so it resolves to a GitHub URL by design. +const ALLOWED_GIT_PREFIX = 'git+ssh://git@github.com/mathworks/'; + +// Local-only files, kept out of the tree by .gitignore. Ignore rules do not apply to +// files already in the index, so a single `git add -f` — or a tool that does the +// equivalent — makes one tracked permanently and .gitignore goes on looking correct. +// These are the internal notes, so that is the leak with the highest cost. +const LOCAL_ONLY = ['CLAUDE.md', 'docs/superpowers/', 'docs/deep-work/', '.superpowers/']; + +// Every phase runs and the exit code is decided at the end, so one failure does not +// mask what the later phases would have found. Reporting every leak at once matters +// when the caller is unattended and only gets one shot at the output. +let failed = false; +if (!checkNeedles()) failed = true; +if (!checkLockfileRegistry()) failed = true; +if (!checkLocalOnlyUntracked()) failed = true; +process.exit(failed ? 1 : 0); + +// `git grep` rather than a filesystem walk: it scans tracked files only, so it needs +// no exclusion list for node_modules, dist/, or the gitignored notes — and "tracked" +// is exactly the set that can reach the remote. This file is excluded from its own +// scan because it necessarily spells the needles out as literals. +// +// Case-SENSITIVE, deliberately. Adding -i looks strictly safer and is not: `ipws` is +// four characters and turns up inside ordinary camelCase, so -i flags every call to +// `skipWs()` in src/webview/rowFilter.ts. A check that cries wolf on correct code gets +// muted, and a muted check guards nothing. The cost is that a shouted `GITLAB` would +// pass; the needles are lowercase everywhere they legitimately occur. +function checkNeedles() { + let hits = ''; + try { + hits = execFileSync( + 'git', + ['grep', '-nI', '-E', NEEDLES.join('|'), '--', '.', ':(exclude)scripts/leak-check.mjs'], + { encoding: 'utf8' }, + ); + } catch (e) { + // git grep exits 1 when nothing matched — the success case. + if (e.status === 1) { + console.log(`OK: none of ${NEEDLES.length} internal needles appear in a tracked file`); + return true; + } + throw e; + } + + if (hits.trim()) { + console.error('LEAK FAIL — internal references found in tracked files:'); + console.error(hits); + console.error('Genericize the wording, or move the file out of the tree.'); + return false; + } + console.log(`OK: none of ${NEEDLES.length} internal needles appear in a tracked file`); + return true; +} + +// Walk the whole lockfile for `resolved` keys rather than reading the `packages` map +// directly: lockfileVersion 3 keeps them under `packages`, but v2 mirrors them under a +// legacy `dependencies` tree, and a walk covers a format change without noticing one. +function checkLockfileRegistry() { + let lock; + try { + lock = JSON.parse(readFileSync('package-lock.json', 'utf8')); + } catch (e) { + console.error(`LEAK CHECK INCONCLUSIVE — package-lock.json is unreadable: ${e.message}`); + return false; + } + + const bad = []; + let checked = 0; + const walk = (node, path) => { + if (!node || typeof node !== 'object') return; + for (const [key, value] of Object.entries(node)) { + if (key === 'resolved' && typeof value === 'string') { + checked += 1; + if (!value.startsWith(PUBLIC_REGISTRY) && !value.startsWith(ALLOWED_GIT_PREFIX)) { + bad.push(`${path || ''}: ${value}`); + } + } else if (value && typeof value === 'object') { + walk(value, `${path}/${key}`); + } + } + }; + walk(lock, ''); + + if (bad.length > 0) { + const subject = bad.length === 1 ? 'entry does not' : `entries do not`; + console.error(`LEAK FAIL — ${bad.length} lockfile ${subject} resolve to the public registry:`); + for (const entry of bad) console.error(` ${entry}`); + console.error( + `\nRewrite each \`resolved\` host to ${PUBLIC_REGISTRY} — the integrity hashes are\n` + + 'content-based and stay valid. An internal host here both names internal\n' + + 'infrastructure and breaks `npm ci` for everyone outside it.', + ); + return false; + } + console.log(`OK: all ${checked} lockfile resolutions point at the public registry`); + return true; +} + +// `git ls-files` asks the index, which is the question that matters: .gitignore says +// what WOULD be ignored, the index says what will actually be pushed. +function checkLocalOnlyUntracked() { + const tracked = execFileSync('git', ['ls-files', '--', ...LOCAL_ONLY], { encoding: 'utf8' }) + .split('\n') + .filter(Boolean); + + if (tracked.length > 0) { + console.error('LEAK FAIL — local-only files are tracked and would be pushed:'); + for (const file of tracked) console.error(` ${file}`); + console.error( + '\nThese hold internal notes. Run `git rm --cached ` — and if any commit\n' + + 'already containing one has been pushed, the history needs rewriting too.', + ); + return false; + } + console.log(`OK: none of ${LOCAL_ONLY.length} local-only paths are tracked`); + return true; +} From 64c4521eca23102e8238221097020d607ab26737 Mon Sep 17 00:00:00 2001 From: Wei Wang Date: Fri, 18 Sep 2026 16:03:01 -0400 Subject: [PATCH 2/2] Add a CHANGELOG, backfilled from the release tags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Eighty releases with no changelog, so the Marketplace page and every GitHub Release showed no history. Reconstructed from the annotated tags: their subject where they have one, then the annotation body, then the commit subjects the tag covers with bump and merge noise dropped. Five of the eighty still say nothing useful — those tags genuinely recorded only a version number. From here the entry is written at bump time, beside the version change. --- CHANGELOG.md | 341 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 341 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..96c771f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,341 @@ +# Change Log + +All notable changes to the Simulink Data Explorer extension. Versions follow the +`vX.Y.Z` tags in this repository; each tag builds the `.vsix` attached to its +GitHub Release. + +Entries before this file existed were reconstructed from those release tags. + +## [1.24.3] — 2026-09-18 + +An array inside a cell in a binary .sldd + +## [1.24.2] — 2026-09-18 + +Read a string in a cell as a string in the binary dictionary + +## [1.24.1] — 2026-09-18 + +The search box works on read-only views (.slx, .mdl, .mat, .prj) + +## [1.24.0] — 2026-09-17 + +A filter condition that contains whitespace is now one condition + +## [1.23.0] — 2026-09-16 + +Schema parity gaps from the MATLAB Data Explorer app + +## [1.22.0] — 2026-09-16 + +Read each file once: a dictionary scanned once per version, a model's structure off a parse already held + +## [1.21.0] — 2026-09-16 + +Parse a file once per content change, not once per consumer + +## [1.20.1] — 2026-09-15 + +A Data Type link jumps to the definition, not to a same-named child row + +## [1.20.0] — 2026-09-15 + +Data Type links to its type definition + +## [1.19.1] — 2026-09-13 + +The same extension, with its rules where they can be reused + +## [1.19.0] — 2026-09-13 + +A folder's indexes read only what they need + +## [1.18.2] — 2026-09-11 + +Configurations refuses what it cannot hold + +## [1.18.1] — 2026-09-11 + +A paste selects every entry it added + +## [1.18.0] — 2026-09-11 + +Multi-select row actions + +## [1.17.1] — 2026-09-10 + +Say how many entries each section holds + +## [1.17.0] — 2026-09-10 + +Draw a link only where there is somewhere to go + +## [1.16.0] — 2026-09-10 + +The Usage column now follows a mask. A `Gain = g1` inside a masked subsystem + +## [1.15.2] — 2026-09-10 + +Keep the string class when a Simulink.Parameter Value is retyped + +## [1.15.1] — 2026-09-10 + +No user-visible change. The two file-name reductions (refModelExt, projectNameOf) + +## [1.15.0] — 2026-09-10 + +A shaped value inside a cell keeps its shape, a multi-row cell or string + +## [1.14.1] — 2026-09-09 + +A table edit writes only the bytes it changes and repaints only the entry it touched; the copied row keeps its ring across the frozen Name column + +## [1.13.1] — 2026-09-09 + +A rename carries the System Composer catalog; the cells the format cannot keep are refused + +## [1.13.0] — 2026-09-09 + +Let a table edit repaint the entry it just wrote + +## [1.12.1] — 2026-09-09 + +Keep the search bar still while the table is still loading + +## [1.12.0] — 2026-09-09 + +Freeze the Name column, and scroll the table sideways under it + +## [1.11.0] — 2026-09-09 + +Entry-scoped model updates and repaints + +## [1.10.4] — 2026-09-08 + +Entry-scoped table repaint for binary .sldd edits + +## [1.10.3] — 2026-09-08 + +Bounded workspace scans: opening a folder of large dictionaries no longer kills the extension host + +## [1.10.2] — 2026-09-08 + +A block name's qualifier now truncates before the name does. In a Name column + +## [1.10.1] — 2026-09-08 + +Two Usage-column fixes + +## [1.10.0] — 2026-09-08 + +Every block is searchable, and each one is somewhere + +## [1.9.2] — 2026-09-08 + +A block is its SID: nameless blocks render , same-named blocks stay separate + +## [1.9.1] — 2026-09-08 + +A shadowed dictionary entry no longer shows a Usage link + +## [1.9.0] — 2026-09-08 + +One answer for the Usage column + +## [1.8.3] — 2026-09-06 + +An object array reads as the class it is an array of + +## [1.8.2] — 2026-09-06 + +Objects carry the object icon, and the tree's icons ship in the VSIX + +## [1.8.1] — 2026-09-05 + +A file that opens short says so + +## [1.8.0] — 2026-09-04 + +.mdl model support + +## [1.7.0] — 2026-09-04 + +Variable Editor: open a matrix value as a floating grid with a (:,:,k) page selector + +## [1.6.5] — 2026-09-02 + +Pick up data-explorer-core 0.1.7: an array's element rows now carry the + +## [1.6.4] — 2026-09-02 + +Fix the table not tracking its panel width after a column resize. Column + +## [1.6.3] — 2026-09-02 + +Bump the data-explorer-core pin to v0.1.5 and fix the defects found + +## [1.6.2] — 2026-08-31 + +Maintenance release. + +## [1.6.1] — 2026-08-31 + +- Delete the src/dex tree; tests now import data-explorer-core only + +## [1.6.0] — 2026-08-31 + +Adopt data-explorer-core package + native webview UI + +## [1.5.10] — 2026-08-19 + +- Remove Marketplace auto-publish from release workflow + +## [1.5.9] — 2026-08-19 + +- Expand object arrays across all formats; fix nested-array truncation +- Fix singleFileUsage integration test for empty workspace source label + +## [1.5.8] — 2026-08-19 + +- Broaden block-param capture (issue #9) + Usage display cleanups; bump to 1.5.8 +- Resolve intra-model Usage for single-file (no-folder) opens + +## [1.5.7] — 2026-08-19 + +- Schema-driven PI layout: common 'General' group across all node types + +## [1.5.6] — 2026-08-14 + +- Publish to VS Code Marketplace on release +- Add Ctrl+F shortcut to focus the table search bar + +## [1.5.5] — 2026-08-14 + +Fix scroll-to-selected on large virtualized tables; center the selected row when scrolling it into view. + +## [1.5.4] — 2026-08-14 + +Expand custom MATLAB class objects; class property names read-only (issue #3) + +## [1.5.3] — 2026-08-14 + +Expand custom MATLAB class objects in the tree (issue #3) + +## [1.5.2] — 2026-08-14 + +- Fix Dependabot devDependency vulnerabilities + +## [1.5.1] — 2026-08-14 + +- Add systematic MATLAB fidelity docs, hardening, and round-trip tests +- Mirror MATLAB element property behavior for Element-level nodes +- Mirror MATLAB setPropValue constraints for editable value/codegen props + +## [1.5.0] — 2026-08-12 + +Constant node for Architectural Data + +## [1.4.1] — 2026-08-12 + +- Docs: describe .sldd as editable regardless of format; bump to 1.4.1 +- Move binary-sldd design spec and plan to local-only docs/deep-work + +## [1.4.0] — 2026-08-12 + +Editable compressed-binary .sldd + +## [1.3.1] — 2026-08-12 + +- Refactor: extract shared common/ modules, reduce duplication +- Remove internal deep-work doc from the public tree + +## [1.3.0] — 2026-08-11 + +- Cover structuralIndex .prj branch and error path +- Add integration test for the lazy-cut single-undo contract +- Add cut/paste end-to-end tests (lazy-cut composition) + +## [1.2.13] — 2026-08-11 + +- Fix CI: repoint arch-paste tests to a committed fixture +- Fix copy/paste + Kind for arch data; add keyboard shortcuts; bump to 1.2.13 + +## [1.2.12] — 2026-08-11 + +- Generate a fresh uuid when pasting an entry; bump to 1.2.12 + +## [1.2.11] — 2026-08-11 + +- Add Child on a ServiceInterface creates a FunctionElement; bump to 1.2.11 + +## [1.2.10] — 2026-08-11 + +- Fix Add Child/Remove Child correctness for text sldd; bump to 1.2.10 + +## [1.2.9] — 2026-08-10 + +- Drop the Jump-to-Reference Navigation section from README; bump to 1.2.9 + +## [1.2.8] — 2026-08-10 + +- Improve README for the Marketplace listing; bump to 1.2.8 + +## [1.2.7] — 2026-08-10 + +- Add Marketplace keywords and Data Science category; bump to 1.2.7 + +## [1.2.6] — 2026-08-10 + +- Rename extension id to simulink-data-explorer; bump to 1.2.6 + +## [1.2.5] — 2026-08-10 + +- Open Model Reference / External Data links on click; bump to 1.2.5 + +## [1.2.4] — 2026-08-10 + +- Add loading spinner; route >512MB JSON .sldd to text editor; bump to 1.2.4 + +## [1.2.3] — 2026-08-10 + +- Rename customer-visible "Simulink Project" to "MATLAB Project"; bump to 1.2.3 + +## [1.2.2] — 2026-08-10 + +- Add Marketplace icon; shorten displayName; bump to 1.2.2 + +## [1.2.1] — 2026-08-10 + +- Route oversized JSON .sldd to read-only view; refresh on save + +## [1.2.0] — 2026-08-10 + +Release v1.2.0: format-independent element-name coloring + +## [1.1.2] — 2026-08-07 + +- docs: add install instructions to README Getting Started +- docs: remove Release Notes section from README + +## [1.1.1] — 2026-08-07 + +Maintenance release. + +## [1.1.0] — 2026-08-07 + +Maintenance release. + +## [1.0.3] — 2026-08-07 + +- Route Simulink.VariantConfigurations to VariantConfiguration; empty ConfigSet Value +- Empty non-editable Value for value-less object nodes; close column menu on blur +- Separate Class/Kind/Data Type; add column customization menu + +## [1.0.2] — 2026-08-06 + +- test(integration): fix viewAsText active-editor race +- Refine Architectural Data presentation; release v1.0.2 + +## [1.0.1] — 2026-08-03 + +Maintenance release.