@threestarsoftware/tssgrid 0.1.26 → 0.1.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,11 @@
3
3
  TssGrid の各リリースの変更点。日付は JST。形式は [Keep a Changelog](https://keepachangelog.com/ja/1.1.0/) 準拠(ゆるめ)。
4
4
  数値は特記なき限り**自社調べ**(headless Chrome / Windows 11・環境差あり)。
5
5
 
6
+ ## [0.1.27] - 2026-10-14
7
+
8
+ ### セキュリティ修正
9
+ - **`rowClass` / `cellClass` の戻り値がエスケープされず `class` 属性に差し込まれる XSS を修正**(コア)— `rowClass` が返すクラス名が `<tr class="...">` に生で入っており(`data-rcls` は esc 済みだが class 側は未処理)、利用者データ(行の値/`src`)由来のクラス名に `"` を含めると属性を差し込める(`onmouseover` 等=XSS)。`cellClass`→`class`/`classList.add` も同型。**`_cellClasses`/`_rowClassStr` が返すクラストークンを「安全なクラス名(空白や `" ' < > &` を含まない)」だけに絞る**ように修正(class 属性・`classList.add` の両経路に有効。Tailwind 風の `/` `:` `.` や Unicode クラスは保持)。あわせて **`TssGrid.esc` が `"` `'` も escape** するように(従来 `& < >` のみ)=`data-rcls`/`data-ccls`/`title` 等 esc 済み属性の `"` 抜けも根から解消。正規のクラスでは挙動不変。
10
+
6
11
  ## [0.1.26] - 2026-10-14
7
12
 
8
13
  ### 追加
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@threestarsoftware/tssgrid",
3
- "version": "0.1.26",
3
+ "version": "0.1.27",
4
4
  "description": "軽量・ノービルド・日本語IME堅牢な編集グリッド(vanilla JS / 依存なし / MIT)。Excel ライクな入力画面を <script> タグ1本で。",
5
5
  "license": "MIT",
6
6
  "author": "ThreeStarSoftware (https://www.threestarsoftware.co.jp/)",
package/src/tssgrid.d.ts CHANGED
@@ -525,7 +525,7 @@ export declare class TssGrid {
525
525
  * number/date/time の組込 parse で使うほか、カスタム `parse`/`format` から `TssGrid.zen2han(v)` として呼べる。 */
526
526
  static zen2han(s: string): string;
527
527
 
528
- /** HTML エスケープ(`&` `<` `>`)。`format` で HTML を組む時などに。 */
528
+ /** HTML エスケープ(`&` `<` `>` `"` `'`)。`format` で HTML/属性を組む時などに(属性値も安全)。 */
529
529
  static esc(s: any): string;
530
530
 
531
531
  [k: string]: any;
package/src/tssgrid.js CHANGED
@@ -867,7 +867,14 @@
867
867
  if (this.height) this.wrap.style.maxHeight = this.height; // 高さ制約(縦スクロール用)
868
868
  }
869
869
 
870
- static esc(s) { return String(s).replace(/[&<>]/g, m => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;' }[m])); }
870
+ static esc(s) { return String(s).replace(/[&<>"']/g, m => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[m])); }
871
+ // rowClass/cellClass が返す値を「CSS クラス名として安全なトークン配列」に。class 属性へ生で入る+classList.add
872
+ // するため、空白や " ' < > & を含むトークンは捨てる=属性抜け(XSS)や classList.add の例外を防ぐ。
873
+ static _clsSafe(x) {
874
+ if (!x) return [];
875
+ const toks = (Array.isArray(x) ? x : [x]).filter(Boolean).map(String).join(' ').split(/\s+/);
876
+ return toks.filter(t => t && /^[^\s"'<>&]+$/.test(t));
877
+ }
871
878
  cellEl(r, c) { if (this._merges) { const a = this._anchorOf(r, c); r = a.r; c = a.c; } return this.table.querySelector('td[data-r="' + r + '"][data-c="' + c + '"]'); }
872
879
  activeTd() { return this.cellEl(this.active.r, this.active.c); }
873
880
  clampR(r) { return Math.max(0, Math.min(this.ROWS - 1, r)); }
@@ -1215,7 +1222,7 @@
1215
1222
  const cc = this.colCfg(c).cellClass;
1216
1223
  if (typeof cc === 'function') { try { push(cc(v, { r, c, row: this.data[r], src })); } catch (_) {} }
1217
1224
  if (typeof this.cellClass === 'function') { try { push(this.cellClass(r, c, v, this.data[r], src)); } catch (_) {} } // row=値配列, src=元レコード
1218
- return out.filter(Boolean);
1225
+ return TssGrid._clsSafe(out); // クラス名として安全なトークンのみ(class 属性/classList.add へ生で入るため)
1219
1226
  }
1220
1227
  // 条件付きクラスをセルに反映(前回分 data-ccls を外してから付け直す=値変更で追従)。
1221
1228
  _applyCellClass(td, r, c) {
@@ -1228,7 +1235,8 @@
1228
1235
  // 行クラス: rowClass(r, row, src) → 空白区切り文字列(配列は join)。無効/例外は ''。
1229
1236
  _rowClassStr(r) {
1230
1237
  if (typeof this.rowClass !== 'function') return '';
1231
- try { const x = this.rowClass(r, this.data[r], this._src ? this._src[r] : null); return x ? (Array.isArray(x) ? x.filter(Boolean).join(' ') : String(x).trim()) : ''; } catch (_) { return ''; }
1238
+ let x; try { x = this.rowClass(r, this.data[r], this._src ? this._src[r] : null); } catch (_) { return ''; }
1239
+ return TssGrid._clsSafe(x).join(' '); // クラス名として安全なトークンのみ(<tr class>/classList.add へ生で入るため)
1232
1240
  }
1233
1241
  // <tr> の行クラスを反映(前回分 data-rcls を外して付け直す=セル再描画で追従)。
1234
1242
  _applyRowClass(r) {