From 9d32ad93cc20e5a61a25e1dbdb2dfca0ee753120 Mon Sep 17 00:00:00 2001 From: Masaharu Hayashi Date: Mon, 24 Aug 2026 22:42:11 +0000 Subject: [PATCH 1/2] =?UTF-8?q?feat(tools):=20=E5=8F=B0=E5=B8=B3=E3=81=B8?= =?UTF-8?q?=E3=81=AE=E8=A1=8C=E8=BF=BD=E5=8A=A0=E3=82=92=E6=94=AF=E6=8F=B4?= =?UTF-8?q?=E3=81=99=E3=82=8B=20add=5Frow.py=20=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit reconcile が未収載を検出しても、57列を手で並べる必要があり実務的でなかった。 api_snapshot.json(実機url_map)と git から機械的に決まる27列を埋め、調査が要る 31列に TODO を入れた雛形を出力する。--append で full.tsv に追記する。 自動: no/module/api_type/app/method/uri/path_params/blueprint/endpoint/ impl_func/impl_file/impl_line/auth_required/auth_method/auth_mechanism/ api_version/last_commit系4列 ほか TODO: summary/data_op/dynamic_verified/sec_* など、ソースを読まないと書けない列 scripts/README.md のケース2を、この手順に沿って具体化した。あわせてケース2b (既存行の修正)を分け、派生列は手編集しても消えることを明記した。 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01HXo9u6PoTf6VRKr3aiGvZ3 --- tools/api-inventory/scripts/README.md | 57 +++++++- tools/api-inventory/scripts/add_row.py | 195 +++++++++++++++++++++++++ 2 files changed, 245 insertions(+), 7 deletions(-) create mode 100644 tools/api-inventory/scripts/add_row.py diff --git a/tools/api-inventory/scripts/README.md b/tools/api-inventory/scripts/README.md index 454b494300..f7ad0bbbc8 100644 --- a/tools/api-inventory/scripts/README.md +++ b/tools/api-inventory/scripts/README.md @@ -57,27 +57,69 @@ python3 tools/api-inventory/scripts/prioritize.py # 優先度・整理対 python3 tools/api-inventory/scripts/build_checklist.py # 24列版を再生成 ``` -## ケース2: 台帳に行を追加・修正する +## ケース2: 台帳に行を追加する -`reconcile.py` が「A. インベントリ未収載」を出したときなど。 +`reconcile.py` が「A. インベントリ未収載」を出したとき。57列を手で並べる必要はない。 ```bash -# 1) full.tsv を直接編集(65列。列数を合わせること) +# 1) 何が未収載かを確認する +python3 tools/api-inventory/scripts/reconcile.py +# → A. インベントリ未収載 に endpoint 名が出る + +# 2) 雛形を確認する(まだ書き込まない) +python3 tools/api-inventory/scripts/add_row.py --endpoint api:weko_admin.foo +# URI の一部でも探せる: --uri /api/items/import-task + +# 3) 追記する +python3 tools/api-inventory/scripts/add_row.py --endpoint api:weko_admin.foo --append + +# 4) TODO の列を埋める(下記) vi "$WEKO_API_INVENTORY_DIR/weko3_api_list_full.tsv" -# 2) 列数の検算 +# 5) 列数の検算 awk -F'\t' 'NR>1 && NF!=65{print "行"NR" 列数="NF}' \ "$WEKO_API_INVENTORY_DIR/weko3_api_list_full.tsv" -# 3) 派生列を再計算 → 24列版を再生成 +# 6) 派生列を再計算 → 24列版を再生成 → 突き合わせ python3 tools/api-inventory/scripts/test_coverage.py python3 tools/api-inventory/scripts/prioritize.py python3 tools/api-inventory/scripts/build_checklist.py +python3 tools/api-inventory/scripts/reconcile.py --gate # 差分0になること +``` + +### add_row.py が埋める列 / 埋めない列 + +`api_snapshot.json`(実機 url_map)と git から**機械的に決まる27列**を埋め、 +調査が要る31列に `TODO` を入れる。 + +| 自動(27列) | no / module / api_type / app / method / uri / path_params / blueprint / endpoint / impl_func / impl_file / impl_line / auth_required / auth_method / auth_mechanism / api_version / last_commit系4列 ほか | +|---|---| +| **`TODO`(31列)** | **summary / response / status_codes / exceptions / roles / auth_response_variance / restricted_content / data_op / data_target / data_store / side_effects / config_deps / test_file / category_tags / notes / sec_* / dynamic_verified / csrf_protection / input_validation / audit_logged / triggers_task / resource_limit / redirect_target / ssrf_surface / idempotency / data_op_detail / bola_risk** | + +`TODO` は **ソースを読まないと書けない列**。Phase 2(静的解析)と Phase 3(実機実測)で +やっていることを、その1行について行う。埋め方は列定義 README(秘密側の +`weko3_api_list_full_README.md`)の各列の説明に従う。 -# 4) 実機と一致するか確認(差分0になること) -python3 tools/api-inventory/scripts/reconcile.py --gate +派生列(`priority` / `test_*` / `cleanup`)は空のままでよい。手順6で自動的に付く。 + +**`TODO` を残したままにしない。** 残っていると優先度判定の入力が欠けるため、 +`prioritize.py` が誤った区分を付ける(例: `data_op` が `TODO` だと破壊系の判定に入らない)。 +調査が終わるまでは、少なくとも `data_op` / `auth_required` / `dynamic_verified` を +埋めること。 + +## ケース2b: 既存行を修正する + +```bash +vi "$WEKO_API_INVENTORY_DIR/weko3_api_list_full.tsv" # 本体列(1-57)だけを直す +python3 tools/api-inventory/scripts/test_coverage.py +python3 tools/api-inventory/scripts/prioritize.py +python3 tools/api-inventory/scripts/build_checklist.py ``` +派生列(58-65)は手で直しても次の実行で消える。優先度を変えたいときは、 +判定の入力側(`security_finding` / `dynamic_verified` / `data_op` / `deprecated`)を +直すか、`prioritize.py` のルールを変える。 + ## ケース3: WEKO3 のバージョンアップに伴う全面更新 ```bash @@ -113,6 +155,7 @@ git push origin main --follow-tags | `test_coverage.py` | full.tsv + テストコード | full.tsv の 60-64列 | | `prioritize.py` | full.tsv | full.tsv の 58-59, 65列 + 末尾列順の正規化 | | `build_checklist.py` | full.tsv | **`weko3_api_list.tsv` を全体再生成** | +| `add_row.py` | `api_snapshot.json` + git | full.tsv に新規行の雛形を追記(`--append`) | `test_coverage.py` → `prioritize.py` → `build_checklist.py` は**何度流しても結果が変わらない** (冪等)。24列版は full.tsv から完全に再現できることを確認済み。 diff --git a/tools/api-inventory/scripts/add_row.py b/tools/api-inventory/scripts/add_row.py new file mode 100644 index 0000000000..626ac2b034 --- /dev/null +++ b/tools/api-inventory/scripts/add_row.py @@ -0,0 +1,195 @@ +# -*- coding: utf-8 -*- +"""台帳に新規行の雛形を追加する。 + + # reconcile が「A. インベントリ未収載」に挙げた経路を追加する + python3 add_row.py --endpoint api:weko_admin.get_widget_item_list + python3 add_row.py --uri /api/items/import-task --append + +`api_snapshot.json`(実機 url_map)と git から **機械的に決まる列だけを埋め**、 +調査が要る列は `TODO` を入れて出力する。57列を手で並べる作業をなくすためのもの。 + +自動で埋まる: no / module / api_type / app / method / uri / path_params / + blueprint / endpoint / impl_func / impl_file / impl_line / + auth_required / auth_method / auth_mechanism / api_version / + last_commit系4列 +`TODO` のまま残る: summary / response / status_codes / roles / data_op / + data_target / data_store / side_effects / config_deps / + test_file / notes / sec_* / dynamic_verified など、 + **ソースを読まないと書けない列**。 + +派生列(priority / test_* / cleanup)は空のままでよい。後で +test_coverage.py → prioritize.py が付与する。 +""" +import argparse +import json +import os +import re +import subprocess +import sys + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from paths import data_path # noqa: E402 +from snapshot import default_weko_root # noqa: E402 + +AUTO_TODO = 'TODO' +DERIVED = ('priority', 'priority_reason', 'test_normal', 'test_abnormal', + 'test_boundary', 'test_exception', 'test_gap', 'cleanup') + + +def sh(args): + return subprocess.run(args, capture_output=True, text=True).stdout + + +def module_of(impl_file): + m = re.match(r'modules/([^/]+)/', impl_file or '') + if m: + return m.group(1) + if impl_file.startswith('(site-packages)'): + return impl_file.split('/')[0].replace('(site-packages)', '').strip() or '-' + return '-' + + +def api_type_of(app, uri, view): + if 'flask_admin' in (view or ''): + return '管理画面(ModelView自動生成)' + if uri.startswith('/admin/'): + return '管理画面' + if app == 'api': + return 'REST API' + return '画面ビュー' + + +def git_last(root, impl_file, impl_line): + """実装関数の行範囲の最終コミット。enrich_git.py と同じ考え方。""" + if not impl_file or impl_file.startswith('(site-packages)') or not impl_line: + return ['-', '-', '-', '-'] + try: + ln = int(impl_line) + except ValueError: + return ['-', '-', '-', '-'] + out = sh(['git', '-C', root, 'log', '-1', '--format=%h\x1f%ad\x1f%s', + '--date=short', '-L', f'{ln},{ln + 40}:{impl_file}']) + line = out.split('\n', 1)[0] if out else '' + p = line.split('\x1f') + if len(p) != 3: + return ['-', '-', '-', '-'] + tags = sh(['git', '-C', root, 'tag', '--sort=creatordate', '--contains', p[0]]) + tag = next((t for t in tags.split('\n') if t.strip()), '(未リリース)') + return [p[0], p[1], p[2].replace('\t', ' ')[:120], tag] + + +def build(hdr, snap_key, e, root, next_no): + app = 'APIアプリ(/api)' if e['app'] == 'api' else 'UIアプリ' + prefix = '/api' if e['app'] == 'api' else '' + rules = e.get('routes') or [{'rule': r, 'methods': e.get('methods', [])} + for r in e.get('rules', [])] + uri = ';'.join(prefix + r['rule'] for r in rules) + methods = sorted({m for r in rules for m in r['methods']}) + impl = e.get('impl', '') + impl_file, impl_line = (impl.split(':') + [''])[:2] if impl else ('', '') + params = ';'.join(sorted({m for r in rules + for m in re.findall(r'<([^>]+)>', r['rule'])})) or '-' + decs = e.get('auth_decorators') or [] + if decs: + auth_req, auth_method = '要', ';'.join(decs) + mech = 'decorator' + elif e.get('attrs') == 'unknown': + auth_req, auth_method, mech = AUTO_TODO, AUTO_TODO + '(属性不明。実装を読むこと)', 'framework' + else: + auth_req, auth_method, mech = '不要', 'none', 'none' + + v = {n: AUTO_TODO for n in hdr} + for n in DERIVED: + if n in v: + v[n] = '' + v.update({ + 'no': str(next_no), + 'module': module_of(impl_file), + 'api_type': api_type_of(e['app'], uri, e.get('view', '')), + 'app': app, + 'method': ','.join(methods), + 'uri': uri, + 'path_params': params, + 'blueprint': e['endpoint'].rsplit('.', 1)[0] if '.' in e['endpoint'] else e['endpoint'], + 'endpoint': e['endpoint'], + 'impl_func': (e.get('view') or '').split('.')[-1] or AUTO_TODO, + 'impl_file': impl_file or (f"(provider){e['provider']}" if e.get('provider') else AUTO_TODO), + 'impl_line': impl_line or '-', + 'auth_required': auth_req, + 'auth_method': auth_method, + 'auth_mechanism': mech, + 'api_version': 'v1' if '/v1' in uri or '' in uri else '-', + 'deprecated': '-', + 'query_params': '-', 'body_params': '-', 'request_content_type': '-', + 'oauth_scope': '-', 'cache_ratelimit': '-', + }) + g = git_last(root, impl_file, impl_line) + for n, val in zip(('last_commit', 'last_commit_date', 'last_commit_subject', + 'release_tag'), g): + if n in v: + v[n] = val + return [v[n] for n in hdr] + + +def main(): + p = argparse.ArgumentParser(description='台帳に新規行の雛形を作る') + p.add_argument('--endpoint', help='api_snapshot.json のキー(例 api:weko_admin.foo)') + p.add_argument('--uri', help='URI の一部で検索して特定する') + p.add_argument('--snapshot', default=None) + p.add_argument('--full', default=None) + p.add_argument('--weko-root', default=None) + p.add_argument('--append', action='store_true', help='full.tsv に追記する(既定は表示のみ)') + a = p.parse_args() + snap_p = a.snapshot or data_path('api_snapshot.json') + full = a.full or data_path('weko3_api_list_full.tsv') + root = a.weko_root or default_weko_root() + + E = json.load(open(snap_p, encoding='utf-8'))['endpoints'] + keys = [] + if a.endpoint: + keys = [a.endpoint] if a.endpoint in E else [] + elif a.uri: + # APIアプリのルールは url_map 上 /api を含まない(DispatcherMiddleware で + # マウントされるため)。利用者は /api/... で探すので前置してから比較する。 + def full_rules(v): + pre = '/api' if v['app'] == 'api' else '' + return [pre + r['rule'] for r in v.get('routes', [])] + keys = [k for k, v in E.items() + if any(a.uri in r for r in full_rules(v))] + if not keys: + sys.exit('該当する経路がスナップショットにありません。' + '--endpoint か --uri を見直してください。') + if len(keys) > 1 and not a.append: + print('複数該当:') + for k in keys: + print(' ' + k) + + lines = open(full, encoding='utf-8').read().rstrip('\n').split('\n') + hdr = lines[0].split('\t') + next_no = max(int(l.split('\t')[0]) for l in lines[1:] + if l.split('\t')[0].isdigit()) + 1 + + rows = [] + for k in keys: + rows.append(build(hdr, k, E[k], root, next_no)) + next_no += 1 + + if a.append: + with open(full, 'a', encoding='utf-8') as f: + for r in rows: + f.write('\t'.join(r) + '\n') + print(f'{full} に {len(rows)} 行を追記しました。') + print(' 次に: TODO の列をソースを読んで埋め、') + print(' test_coverage.py → prioritize.py → build_checklist.py を実行') + else: + for r in rows: + print('--- 追記される行(--append で書き込み) ---') + for n, val in zip(hdr, r): + mark = ' ' if val != AUTO_TODO else '★' + if val != '': + print(f'{mark}{n:<22} {val[:70]}') + print(' ★ が付いた列はソースを読んで埋めること') + + +if __name__ == '__main__': + main() From c6d8b72193fb90fdcf70d18ce433a6244d989240 Mon Sep 17 00:00:00 2001 From: Masaharu Hayashi Date: Mon, 24 Aug 2026 22:50:32 +0000 Subject: [PATCH 2/2] =?UTF-8?q?fix(tools):=20=E6=A9=9F=E6=A2=B0=E4=BB=98?= =?UTF-8?q?=E4=B8=8E=E3=82=B9=E3=82=AF=E3=83=AA=E3=83=97=E3=83=88=E3=81=AE?= =?UTF-8?q?=E5=85=A5=E5=87=BA=E5=8A=9B=E3=82=92=E7=9B=B4=E3=81=97=E3=80=81?= =?UTF-8?q?=E6=97=A2=E5=AD=98=E5=80=A4=E3=82=92=E4=BF=9D=E8=AD=B7=E3=81=99?= =?UTF-8?q?=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit add_cols.py / add_ssrf_redirect.py / add_idempotency.py / add_dataop4.py / add_authmech.py は当時の中間ファイル運用の名残で、入出力が R+"weko3_api_list.tsv"(24列版)にハードコードされていた。いま実行すると 台帳を壊す状態だったため、$WEKO_API_INVENTORY_DIR の full.tsv を in-place 更新するようにした(第1引数でも指定可)。 参照する列位置(c[2]=api_type, c[4]=method, c[13]=impl_file, c[14]=impl_line, c[20]=auth_required, c[21]=auth_method)は現行 full.tsv と一致しており変更不要。 既存列は「その位置のまま値を差し替える」方式にした。末尾に付け直すと列順が 変わり、README の awk 例や他スクリプトの列位置前提が壊れるため。 さらに **空欄/TODO のセルだけを埋める**ようにした。台帳の既存値は機械出力 そのままではなく後から精査されており、一括再生成すると劣化することを実測で 確認したため(bola_risk の判定が逆転、data_op_detail の論理/物理の区別が失われる、 csrf_protection の指摘が消える)。意図的に作り直すときは WEKO_INVENTORY_OVERWRITE=1 を指定する。 あわせて「列数: N」の表示が古い計算式で実際と合っていなかったのを直した。 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01HXo9u6PoTf6VRKr3aiGvZ3 --- tools/api-inventory/scripts/README.md | 21 ++++++++ tools/api-inventory/scripts/add_authmech.py | 50 ++++++++++++++++-- tools/api-inventory/scripts/add_cols.py | 52 +++++++++++++++++-- tools/api-inventory/scripts/add_dataop4.py | 50 ++++++++++++++++-- .../api-inventory/scripts/add_idempotency.py | 50 ++++++++++++++++-- .../scripts/add_ssrf_redirect.py | 52 +++++++++++++++++-- 6 files changed, 253 insertions(+), 22 deletions(-) diff --git a/tools/api-inventory/scripts/README.md b/tools/api-inventory/scripts/README.md index f7ad0bbbc8..687feed8d2 100644 --- a/tools/api-inventory/scripts/README.md +++ b/tools/api-inventory/scripts/README.md @@ -87,6 +87,26 @@ python3 tools/api-inventory/scripts/build_checklist.py python3 tools/api-inventory/scripts/reconcile.py --gate # 差分0になること ``` +### 機械付与スクリプトで TODO を減らす + +`add_row.py --append` の直後に、Phase 2 の機械付与スクリプトを流すと `TODO` が減る。 + +```bash +export WEKO_ROOT=/path/to/weko # 解析対象のソース +python3 tools/api-inventory/scripts/add_cols.py # csrf_protection / input_validation / + # audit_logged / triggers_task / resource_limit +python3 tools/api-inventory/scripts/add_ssrf_redirect.py # redirect_target / ssrf_surface +python3 tools/api-inventory/scripts/add_idempotency.py # idempotency +python3 tools/api-inventory/scripts/add_dataop4.py # data_op_detail +python3 tools/api-inventory/scripts/add_authmech.py # auth_mechanism / bola_risk +``` + +**これらは空欄/`TODO` のセルだけを埋める。既存値は上書きしない。** +台帳の既存値は機械出力そのままではなく後から精査されており、一括再生成すると劣化する +(実測: `bola_risk` の判定が逆転、`data_op_detail` の論理削除/物理削除の区別が失われる、 +`csrf_protection` の指摘が消える)。意図して作り直すときだけ +`WEKO_INVENTORY_OVERWRITE=1` を付ける。 + ### add_row.py が埋める列 / 埋めない列 `api_snapshot.json`(実機 url_map)と git から**機械的に決まる27列**を埋め、 @@ -156,6 +176,7 @@ git push origin main --follow-tags | `prioritize.py` | full.tsv | full.tsv の 58-59, 65列 + 末尾列順の正規化 | | `build_checklist.py` | full.tsv | **`weko3_api_list.tsv` を全体再生成** | | `add_row.py` | `api_snapshot.json` + git | full.tsv に新規行の雛形を追記(`--append`) | +| `add_cols.py` / `add_ssrf_redirect.py` / `add_idempotency.py` / `add_dataop4.py` / `add_authmech.py` | full.tsv + 実装ソース | full.tsv の**空欄/TODO セルのみ**を機械付与 | `test_coverage.py` → `prioritize.py` → `build_checklist.py` は**何度流しても結果が変わらない** (冪等)。24列版は full.tsv から完全に再現できることを確認済み。 diff --git a/tools/api-inventory/scripts/add_authmech.py b/tools/api-inventory/scripts/add_authmech.py index c0fb8fcc28..3a26bd35d9 100644 --- a/tools/api-inventory/scripts/add_authmech.py +++ b/tools/api-inventory/scripts/add_authmech.py @@ -1,9 +1,52 @@ # -*- coding: utf-8 -*- """auth_mechanism(認証の付け方3分類) と bola_risk(object-level認可の有無) を付与""" import ast,re,os -R="/home/mhaya/wekov2/" +import os as _os, sys as _sys +_sys.path.insert(0, _os.path.dirname(_os.path.abspath(__file__))) +from paths import data_path as _data_path + +# 入出力: 既定は $WEKO_API_INVENTORY_DIR/weko3_api_list_full.tsv を in-place 更新。 +# 以前は R+"weko3_api_list.tsv"(24列版)を読み書きしており、いま実行すると +# 台帳を壊す状態だった(当時これが作業用中間ファイルだったころの名残)。 +TSV = _sys.argv[1] if len(_sys.argv) > 1 else _data_path("weko3_api_list_full.tsv") + + +def _write(path, hd, data, newcols): + """既存の同名列は **その位置のまま値を差し替える**。無い列だけ末尾に足す。 + + 末尾に付け直すと列順が変わり、README の awk 例や他スクリプトの + 列位置前提(c[13]=impl_file 等)が壊れる。 + """ + pos = {n: i for i, n in enumerate(hd)} + add_cols = [n for n in newcols if n not in pos] + out_hd = list(hd) + add_cols + force = _os.environ.get("WEKO_INVENTORY_OVERWRITE") == "1" + empty = ("", "-", "TODO") + lines = ["\t".join(out_hd)] + filled = 0 + for c in data: + row = list(c) + [""] * (len(hd) - len(c)) + vals = row[len(hd):] # このスクリプトが今回算出した値 + body = row[:len(hd)] + for n, v in zip(newcols, vals): + if n not in pos: + continue + cur = body[pos[n]] + # 既存値は人手で精査されている。空欄/TODO のセルだけ埋める。 + # 一括再生成すると判定が劣化する(bola_risk が逆転、data_op_detail の + # 論理/物理の区別が失われる、CSRF の指摘が消える等を実測で確認済み)。 + if cur in empty or force: + if cur != v: + filled += 1 + body[pos[n]] = v + extra = [v for n, v in zip(newcols, vals) if n not in pos] + lines.append("\t".join(str(x).replace("\t", " ") for x in body + extra)) + open(path, "w", encoding="utf-8").write("\n".join(lines) + "\n") + print(f" → 空欄/TODO を埋めたセル: {filled}" + + (" (WEKO_INVENTORY_OVERWRITE=1 のため既存値も上書き)" if force else "")) +R = _os.environ.get("WEKO_ROOT", "/home/mhaya/wekov2") + "/" def load(p): return [l.rstrip("\n").split("\t") for l in open(p,encoding="utf-8") if l.rstrip("\n")] -rows=load(R+"weko3_api_list.tsv"); hd=rows[0]; data=rows[1:] +rows=load(TSV); hd=rows[0]; data=rows[1:] filecache={} def get_file(fp): @@ -76,8 +119,7 @@ def col_bola(c,seg): c += [mech,bola] mc[mech.split("(")[0]]+=1; bc[bola.split("(")[0]]+=1 if "★" in bola: nb+=1 -open(R+"weko3_api_list.tsv","w",encoding="utf-8").write("\t".join(hd+["auth_mechanism","bola_risk"])+"\n"+ - "\n".join("\t".join(str(x).replace("\t"," ") for x in c) for c in data)+"\n") +_write(TSV, hd, data, ['auth_mechanism', 'bola_risk']) print("=== auth_mechanism 分布 ==="); [print(f" {n:4d} {k}") for k,n in mc.most_common()] print("=== bola_risk 分布 ==="); [print(f" {n:4d} {k}") for k,n in bc.most_common()] print("列数:",len(hd)+2) diff --git a/tools/api-inventory/scripts/add_cols.py b/tools/api-inventory/scripts/add_cols.py index f7bd8aced6..a1f3c8c1d0 100644 --- a/tools/api-inventory/scripts/add_cols.py +++ b/tools/api-inventory/scripts/add_cols.py @@ -1,9 +1,52 @@ # -*- coding: utf-8 -*- """3観点列(csrf_protection/input_validation/audit_logged/triggers_task/resource_limit)をAST+実装から機械付与""" import ast,re,os,collections -R="/home/mhaya/wekov2/" +import os as _os, sys as _sys +_sys.path.insert(0, _os.path.dirname(_os.path.abspath(__file__))) +from paths import data_path as _data_path + +# 入出力: 既定は $WEKO_API_INVENTORY_DIR/weko3_api_list_full.tsv を in-place 更新。 +# 以前は R+"weko3_api_list.tsv"(24列版)を読み書きしており、いま実行すると +# 台帳を壊す状態だった(当時これが作業用中間ファイルだったころの名残)。 +TSV = _sys.argv[1] if len(_sys.argv) > 1 else _data_path("weko3_api_list_full.tsv") + + +def _write(path, hd, data, newcols): + """既存の同名列は **その位置のまま値を差し替える**。無い列だけ末尾に足す。 + + 末尾に付け直すと列順が変わり、README の awk 例や他スクリプトの + 列位置前提(c[13]=impl_file 等)が壊れる。 + """ + pos = {n: i for i, n in enumerate(hd)} + add_cols = [n for n in newcols if n not in pos] + out_hd = list(hd) + add_cols + force = _os.environ.get("WEKO_INVENTORY_OVERWRITE") == "1" + empty = ("", "-", "TODO") + lines = ["\t".join(out_hd)] + filled = 0 + for c in data: + row = list(c) + [""] * (len(hd) - len(c)) + vals = row[len(hd):] # このスクリプトが今回算出した値 + body = row[:len(hd)] + for n, v in zip(newcols, vals): + if n not in pos: + continue + cur = body[pos[n]] + # 既存値は人手で精査されている。空欄/TODO のセルだけ埋める。 + # 一括再生成すると判定が劣化する(bola_risk が逆転、data_op_detail の + # 論理/物理の区別が失われる、CSRF の指摘が消える等を実測で確認済み)。 + if cur in empty or force: + if cur != v: + filled += 1 + body[pos[n]] = v + extra = [v for n, v in zip(newcols, vals) if n not in pos] + lines.append("\t".join(str(x).replace("\t", " ") for x in body + extra)) + open(path, "w", encoding="utf-8").write("\n".join(lines) + "\n") + print(f" → 空欄/TODO を埋めたセル: {filled}" + + (" (WEKO_INVENTORY_OVERWRITE=1 のため既存値も上書き)" if force else "")) +R = _os.environ.get("WEKO_ROOT", "/home/mhaya/wekov2") + "/" def load(p): return [l.rstrip("\n").split("\t") for l in open(p,encoding="utf-8") if l.rstrip("\n")] -rows=load(R+"weko3_api_list.tsv"); hd=rows[0]; data=rows[1:] +rows=load(TSV); hd=rows[0]; data=rows[1:] # 実装関数のソース断片をキャッシュ srccache={} @@ -68,8 +111,7 @@ def col_reslimit(seg): for c in data: seg=get_src(c[13],c[14]) if c[14].isdigit() else "" c += [col_csrf(c,seg), col_input(seg), col_audit(seg), col_task(seg), col_reslimit(seg)] -open(R+"weko3_api_list.tsv","w",encoding="utf-8").write("\t".join(hd+newcols)+"\n"+ - "\n".join("\t".join(x.replace("\t"," ") for x in c) for c in data)+"\n") +_write(TSV, hd, data, ['csrf_protection', 'input_validation', 'audit_logged', 'triggers_task', 'resource_limit']) # サマリ for i,name in enumerate(newcols): ci=len(hd)+i @@ -78,4 +120,4 @@ def col_reslimit(seg): v=c[ci] if len(c)>ci else "-" cnt["有" if v not in("-","N/A(参照系)","CSRF該当外(未認証public)","OAuth(CSRF非該当)") else "無/N-A"]+=1 print(f"{name}: 有効値={cnt['有']}") -print("列数:",len(hd)+len(newcols)) +print("列数:", len(hd)) diff --git a/tools/api-inventory/scripts/add_dataop4.py b/tools/api-inventory/scripts/add_dataop4.py index 9a92ee4e49..0b95589ab1 100644 --- a/tools/api-inventory/scripts/add_dataop4.py +++ b/tools/api-inventory/scripts/add_dataop4.py @@ -1,9 +1,52 @@ # -*- coding: utf-8 -*- """data_op_detail列: 取得/作成/更新/物理削除/論理削除 を実装から4区分評価""" import ast,re,os -R="/home/mhaya/wekov2/" +import os as _os, sys as _sys +_sys.path.insert(0, _os.path.dirname(_os.path.abspath(__file__))) +from paths import data_path as _data_path + +# 入出力: 既定は $WEKO_API_INVENTORY_DIR/weko3_api_list_full.tsv を in-place 更新。 +# 以前は R+"weko3_api_list.tsv"(24列版)を読み書きしており、いま実行すると +# 台帳を壊す状態だった(当時これが作業用中間ファイルだったころの名残)。 +TSV = _sys.argv[1] if len(_sys.argv) > 1 else _data_path("weko3_api_list_full.tsv") + + +def _write(path, hd, data, newcols): + """既存の同名列は **その位置のまま値を差し替える**。無い列だけ末尾に足す。 + + 末尾に付け直すと列順が変わり、README の awk 例や他スクリプトの + 列位置前提(c[13]=impl_file 等)が壊れる。 + """ + pos = {n: i for i, n in enumerate(hd)} + add_cols = [n for n in newcols if n not in pos] + out_hd = list(hd) + add_cols + force = _os.environ.get("WEKO_INVENTORY_OVERWRITE") == "1" + empty = ("", "-", "TODO") + lines = ["\t".join(out_hd)] + filled = 0 + for c in data: + row = list(c) + [""] * (len(hd) - len(c)) + vals = row[len(hd):] # このスクリプトが今回算出した値 + body = row[:len(hd)] + for n, v in zip(newcols, vals): + if n not in pos: + continue + cur = body[pos[n]] + # 既存値は人手で精査されている。空欄/TODO のセルだけ埋める。 + # 一括再生成すると判定が劣化する(bola_risk が逆転、data_op_detail の + # 論理/物理の区別が失われる、CSRF の指摘が消える等を実測で確認済み)。 + if cur in empty or force: + if cur != v: + filled += 1 + body[pos[n]] = v + extra = [v for n, v in zip(newcols, vals) if n not in pos] + lines.append("\t".join(str(x).replace("\t", " ") for x in body + extra)) + open(path, "w", encoding="utf-8").write("\n".join(lines) + "\n") + print(f" → 空欄/TODO を埋めたセル: {filled}" + + (" (WEKO_INVENTORY_OVERWRITE=1 のため既存値も上書き)" if force else "")) +R = _os.environ.get("WEKO_ROOT", "/home/mhaya/wekov2") + "/" def load(p): return [l.rstrip("\n").split("\t") for l in open(p,encoding="utf-8") if l.rstrip("\n")] -rows=load(R+"weko3_api_list.tsv"); hd=rows[0]; data=rows[1:] +rows=load(TSV); hd=rows[0]; data=rows[1:] # ファイル全体をキャッシュし、関数本体+同ファイル内で呼ぶヘルパも1段追う filecache={} @@ -75,6 +118,5 @@ def eval4(c,seg,method): v=eval4(c,seg,method) c.append(v) if "論理削除" in v or "物理削除" in v: nc+=1 -open(R+"weko3_api_list.tsv","w",encoding="utf-8").write("\t".join(hd+["data_op_detail"])+"\n"+ - "\n".join("\t".join(str(x).replace("\t"," ") for x in c) for c in data)+"\n") +_write(TSV, hd, data, ['data_op_detail']) print("data_op_detail付与。削除系(論理/物理):",nc,"列数:",len(hd)+1) diff --git a/tools/api-inventory/scripts/add_idempotency.py b/tools/api-inventory/scripts/add_idempotency.py index d26567429d..ddefde144c 100644 --- a/tools/api-inventory/scripts/add_idempotency.py +++ b/tools/api-inventory/scripts/add_idempotency.py @@ -1,7 +1,50 @@ import ast,re,os -R="/home/mhaya/wekov2/" +import os as _os, sys as _sys +_sys.path.insert(0, _os.path.dirname(_os.path.abspath(__file__))) +from paths import data_path as _data_path + +# 入出力: 既定は $WEKO_API_INVENTORY_DIR/weko3_api_list_full.tsv を in-place 更新。 +# 以前は R+"weko3_api_list.tsv"(24列版)を読み書きしており、いま実行すると +# 台帳を壊す状態だった(当時これが作業用中間ファイルだったころの名残)。 +TSV = _sys.argv[1] if len(_sys.argv) > 1 else _data_path("weko3_api_list_full.tsv") + + +def _write(path, hd, data, newcols): + """既存の同名列は **その位置のまま値を差し替える**。無い列だけ末尾に足す。 + + 末尾に付け直すと列順が変わり、README の awk 例や他スクリプトの + 列位置前提(c[13]=impl_file 等)が壊れる。 + """ + pos = {n: i for i, n in enumerate(hd)} + add_cols = [n for n in newcols if n not in pos] + out_hd = list(hd) + add_cols + force = _os.environ.get("WEKO_INVENTORY_OVERWRITE") == "1" + empty = ("", "-", "TODO") + lines = ["\t".join(out_hd)] + filled = 0 + for c in data: + row = list(c) + [""] * (len(hd) - len(c)) + vals = row[len(hd):] # このスクリプトが今回算出した値 + body = row[:len(hd)] + for n, v in zip(newcols, vals): + if n not in pos: + continue + cur = body[pos[n]] + # 既存値は人手で精査されている。空欄/TODO のセルだけ埋める。 + # 一括再生成すると判定が劣化する(bola_risk が逆転、data_op_detail の + # 論理/物理の区別が失われる、CSRF の指摘が消える等を実測で確認済み)。 + if cur in empty or force: + if cur != v: + filled += 1 + body[pos[n]] = v + extra = [v for n, v in zip(newcols, vals) if n not in pos] + lines.append("\t".join(str(x).replace("\t", " ") for x in body + extra)) + open(path, "w", encoding="utf-8").write("\n".join(lines) + "\n") + print(f" → 空欄/TODO を埋めたセル: {filled}" + + (" (WEKO_INVENTORY_OVERWRITE=1 のため既存値も上書き)" if force else "")) +R = _os.environ.get("WEKO_ROOT", "/home/mhaya/wekov2") + "/" def load(p): return [l.rstrip("\n").split("\t") for l in open(p,encoding="utf-8") if l.rstrip("\n")] -rows=load(R+"weko3_api_list.tsv"); hd=rows[0]; data=rows[1:] +rows=load(TSV); hd=rows[0]; data=rows[1:] srccache={} def get_src(fp,ln): key=(fp,ln) @@ -34,6 +77,5 @@ def col_idem(c,seg): seg=get_src(c[13],c[14]) if (len(c)>14) else "" v=col_idem(c,seg); c.append(v) if "★" in v: nc+=1 -open(R+"weko3_api_list.tsv","w",encoding="utf-8").write("\t".join(hd+["idempotency"])+"\n"+ - "\n".join("\t".join(str(x).replace("\t"," ") for x in c) for c in data)+"\n") +_write(TSV, hd, data, ['idempotency']) print("idempotency ★状態遷移:",nc,"列数:",len(hd)+1) diff --git a/tools/api-inventory/scripts/add_ssrf_redirect.py b/tools/api-inventory/scripts/add_ssrf_redirect.py index b8363213ca..4ebbb2af23 100644 --- a/tools/api-inventory/scripts/add_ssrf_redirect.py +++ b/tools/api-inventory/scripts/add_ssrf_redirect.py @@ -1,9 +1,52 @@ # -*- coding: utf-8 -*- """redirect_target(オープンリダイレクト面) と ssrf_surface(SSRF面) を実装から機械付与""" import ast,re,os -R="/home/mhaya/wekov2/" +import os as _os, sys as _sys +_sys.path.insert(0, _os.path.dirname(_os.path.abspath(__file__))) +from paths import data_path as _data_path + +# 入出力: 既定は $WEKO_API_INVENTORY_DIR/weko3_api_list_full.tsv を in-place 更新。 +# 以前は R+"weko3_api_list.tsv"(24列版)を読み書きしており、いま実行すると +# 台帳を壊す状態だった(当時これが作業用中間ファイルだったころの名残)。 +TSV = _sys.argv[1] if len(_sys.argv) > 1 else _data_path("weko3_api_list_full.tsv") + + +def _write(path, hd, data, newcols): + """既存の同名列は **その位置のまま値を差し替える**。無い列だけ末尾に足す。 + + 末尾に付け直すと列順が変わり、README の awk 例や他スクリプトの + 列位置前提(c[13]=impl_file 等)が壊れる。 + """ + pos = {n: i for i, n in enumerate(hd)} + add_cols = [n for n in newcols if n not in pos] + out_hd = list(hd) + add_cols + force = _os.environ.get("WEKO_INVENTORY_OVERWRITE") == "1" + empty = ("", "-", "TODO") + lines = ["\t".join(out_hd)] + filled = 0 + for c in data: + row = list(c) + [""] * (len(hd) - len(c)) + vals = row[len(hd):] # このスクリプトが今回算出した値 + body = row[:len(hd)] + for n, v in zip(newcols, vals): + if n not in pos: + continue + cur = body[pos[n]] + # 既存値は人手で精査されている。空欄/TODO のセルだけ埋める。 + # 一括再生成すると判定が劣化する(bola_risk が逆転、data_op_detail の + # 論理/物理の区別が失われる、CSRF の指摘が消える等を実測で確認済み)。 + if cur in empty or force: + if cur != v: + filled += 1 + body[pos[n]] = v + extra = [v for n, v in zip(newcols, vals) if n not in pos] + lines.append("\t".join(str(x).replace("\t", " ") for x in body + extra)) + open(path, "w", encoding="utf-8").write("\n".join(lines) + "\n") + print(f" → 空欄/TODO を埋めたセル: {filled}" + + (" (WEKO_INVENTORY_OVERWRITE=1 のため既存値も上書き)" if force else "")) +R = _os.environ.get("WEKO_ROOT", "/home/mhaya/wekov2") + "/" def load(p): return [l.rstrip("\n").split("\t") for l in open(p,encoding="utf-8") if l.rstrip("\n")] -rows=load(R+"weko3_api_list.tsv"); hd=rows[0]; data=rows[1:] +rows=load(TSV); hd=rows[0]; data=rows[1:] srccache={} def get_src(fp,ln): @@ -50,6 +93,5 @@ def col_ssrf(seg): if "★" in r: nr+=1 if "★" in s: ns+=1 c += [r,s] -open(R+"weko3_api_list.tsv","w",encoding="utf-8").write("\t".join(hd+newcols)+"\n"+ - "\n".join("\t".join(str(x).replace("\t"," ") for x in c) for c in data)+"\n") -print(f"redirect_target ★検証なし:{nr} ssrf_surface ★:{ns} 列数:{len(hd)+2}") +_write(TSV, hd, data, ['redirect_target', 'ssrf_surface']) +print(f"redirect_target ★検証なし:{nr} ssrf_surface ★:{ns} 列数:{len(hd)}")