決定論的解析器、隔離コンテナ実行、およびライブペネトレーションテスト
pni501 におけるすべての定量的メトリクス、依存関係グラフ、テストカバレッジ率、CVE 件数、およびライブ HTTP エクスプロイト検証は、LLM の推論ステップが実行される前に 6 個の決定論的 Python 解析器によって生成されます。6 言語対応の 1 回パース Tree-sitter CST 解析 (ast-grep-py) と、オフライン単体テスト・ソケットレス Trivy スキャン・ライブループバック侵入テストを実行する隔離 Docker/Podman コンテナサンドボックスを組み合わせることで、pni501 はハルシネーションのない 100% 再現可能なエンジニアリング証跡を保証します。
Source Code
from drawlib.canvas import save, setup
from drawlib.diagrams.architecture import ArchitectureDiagram, Node, NodeGroup, PhosphorIcon
from drawlib.styles import Styles
setup(width=176, height=98, dpi=150)
diag = ArchitectureDiagram(
node_style=Styles.PrimaryBold,
node_text_style=Styles.DarkBold.patch(text_size=10.0),
edge_style=Styles.DarkBold,
edge_text_style=Styles.DarkBold.patch(text_size=10.0),
node_card_style=Styles.WhiteBordered.patch(shape_r=1.5),
)
g_in = diag.add(
NodeGroup(
title="1. Parse-Once CST (6 Langs)",
width=40.0,
height=90.0,
style=Styles.Neutral.patch(shape_r=2.0),
text_style=Styles.PrimaryBold.patch(text_size=10.5, halign="left", valign="top"),
),
xy=(3.0, 4.0),
)
n_langs = g_in.add(
Node(
"6 Target Languages\nPy / Java / PHP\nGo / TS / JS",
width=33.0,
height=20.0,
icon=PhosphorIcon.CODE,
icon_size=5.4,
card_style=Styles.PrimaryNeutral.patch(shape_r=1.5),
),
xy=(20.0, 67.0),
)
n_cst = g_in.add(
Node(
"ast-grep-py SgRoot\nImmutable ParsedUnit\n(Zero Code Execution)",
width=33.0,
height=20.0,
icon=PhosphorIcon.TREE_STRUCTURE,
icon_size=5.6,
style=Styles.WhiteBold,
text_style=Styles.WhiteBold.patch(text_size=10.0),
card_style=Styles.PrimaryFlat.patch(shape_r=1.5),
),
xy=(20.0, 41.0),
)
n_ver = g_in.add(
Node(
"version_compat.py\nSyntax vs Manifest\nRuntime Check",
width=33.0,
height=18.0,
icon=PhosphorIcon.SEAL_CHECK,
icon_size=5.2,
card_style=Styles.SecondaryNeutral.patch(shape_r=1.5),
),
xy=(20.0, 15.0),
)
g_chk = diag.add(
NodeGroup(
title="2. Nine Modular Tree-sitter AST Checkers (ast_analyze/base/)",
width=84.0,
height=90.0,
style=Styles.Neutral.patch(shape_r=2.0),
text_style=Styles.DarkBold.patch(text_size=10.5, halign="left", valign="top"),
),
xy=(48.0, 4.0),
)
c1 = g_chk.add(Node("1. Import Hygiene\n(imports.py)", width=24.5, height=17.5, icon=PhosphorIcon.ARROWS_LEFT_RIGHT, icon_size=4.8), xy=(15.5, 67.0))
c2 = g_chk.add(Node("2. Type Safety\n(typing.py)", width=24.5, height=17.5, icon=PhosphorIcon.SHIELD_CHECK, icon_size=4.8), xy=(42.0, 67.0))
c3 = g_chk.add(Node("3. API Docs\n(docs.py)", width=24.5, height=17.5, icon=PhosphorIcon.BOOK_OPEN_TEXT, icon_size=4.8), xy=(68.5, 67.0))
c4 = g_chk.add(Node("4. Code Quality\n(quality.py)", width=24.5, height=17.5, icon=PhosphorIcon.GAUGE, icon_size=4.8), xy=(15.5, 42.0))
c5 = g_chk.add(Node("5. Deps & Cycles\n(dependencies.py)", width=24.5, height=17.5, icon=PhosphorIcon.GRAPH, icon_size=4.8), xy=(42.0, 42.0))
c6 = g_chk.add(Node("6. Test Suite\n(testing.py)", width=24.5, height=17.5, icon=PhosphorIcon.FLASK, icon_size=4.8), xy=(68.5, 42.0))
c7 = g_chk.add(Node("7. Frameworks\n(frameworks.py)", width=24.5, height=17.5, icon=PhosphorIcon.STACK, icon_size=4.8), xy=(15.5, 17.0))
c8 = g_chk.add(Node("8. Operational\n(operational.py)", width=24.5, height=17.5, icon=PhosphorIcon.PULSE, icon_size=4.8), xy=(42.0, 17.0))
c9 = g_chk.add(
Node(
"9. Security Sinks\n(security.py)",
width=24.5,
height=17.5,
icon=PhosphorIcon.WARNING_OCTAGON,
icon_size=4.8,
style=Styles.DangerBold,
card_style=Styles.DangerNeutral.patch(shape_r=1.5),
),
xy=(68.5, 17.0),
)
g_out = diag.add(
NodeGroup(
title="3. Grounded AstFacts",
width=36.0,
height=90.0,
style=Styles.PrimaryNeutral.patch(shape_r=2.0),
text_style=Styles.PrimaryBold.patch(text_size=10.5, halign="left", valign="top"),
),
xy=(137.0, 4.0),
)
n_opt = g_out.add(
Node(
"pni.toml [[opt_out]]\nWaiver Filter",
width=29.5,
height=18.0,
icon=PhosphorIcon.FUNNEL,
icon_size=5.0,
),
xy=(18.0, 67.0),
)
n_inv = g_out.add(
Node(
"module_inventory &\nimport_graph",
width=29.5,
height=19.0,
icon=PhosphorIcon.BLUEPRINT,
icon_size=5.2,
),
xy=(18.0, 42.0),
)
n_facts = g_out.add(
Node(
"AstFacts JSON\ndangerous_sinks &\napi_endpoints",
width=29.5,
height=20.0,
icon=PhosphorIcon.DATABASE,
icon_size=5.4,
style=Styles.SuccessBold,
card_style=Styles.SuccessNeutral.patch(shape_r=1.5),
),
xy=(18.0, 16.0),
)
diag.connect(n_langs, n_cst, routing="orthogonal")
diag.connect(n_cst, n_ver, routing="orthogonal")
diag.connect(g_in, g_chk, routing="orthogonal", style=Styles.PrimaryBold)
diag.connect(g_chk, g_out, routing="orthogonal", style=Styles.PrimaryBold)
diag.connect(n_opt, n_inv, routing="orthogonal")
diag.connect(n_inv, n_facts, routing="orthogonal", style=Styles.SuccessBold)
diag.draw(xy=(0.0, 0.0))
save()
1. 静的リポジトリ検査と Tree-sitter AST 解析器
1.1 リポジトリ・サプライチェーン・ガバナンス検査器 (project_inspect -> ProjectFacts)
src/pni/_actions/project_inspect.py および src/pni/_actions/_repo_governance.py に実装された run_project_inspect() は、外部パッケージマネージャを実行することなく、リポジトリ構造、依存関係、ドキュメント、および OpenSSF サプライチェーンシグナルを抽出します。
- 6言語対応マニフェスト & ロックファイル解析:
pyproject.toml/setup.py/requirements.txt(Python)、pom.xml/build.gradle*/ Gradle バージョンカタログ(Java)、composer.json/ WordPress ヘッダ(PHP)、go.mod/go.sum(Go)、およびpackage.json/tsconfig.json(TypeScript & JavaScript)を解析します。lockfiles_found(uv.lock,poetry.lock,gradle.lockfile,composer.lock,go.sum,package-lock.json,pnpm-lock.yaml,yarn.lock)、unpinned_dependencies、detected_frameworks、runtime_types(["library"],["cli"],["cli", "library"],["web_api"],["worker"])、legacy_language_findings、およびtooling_modernization_issuesを検出します。 - OpenSSF サプライチェーン & リポジトリガバナンス (
_repo_governance.py):has_license、has_security_policy(SECURITY.md)、has_dependency_automation(Dependabot / Renovate)、GitHub Actions のci_supply_chain_issues(可変な@main/@masterアクション参照、permissions:ブロック欠如、肥大化したインライン bash スクリプト)、committed_secret_files(.env,.pem,.key, サービスアカウント JSON)、Diátaxisreadme_headings/docs_inventory、および静的container_hygiene_issues(:latestベースタグ、rootUSER)を監査します。
1.2 1回パース多言語 CST/AST 解析器 (ast_analyze -> AstFacts)
src/pni/_actions/ast_analyze/ に実装された run_ast_analyze() は、信頼できないコードを一切インポート・実行することなく、各対象ファイルを ast-grep-py で 1回だけ不変の ParsedUnit(SgRoot Tree-sitter CST)へパースし、python/, java/, php/, go/, ts_js/(TypeScript + JavaScript)にわたる 9 個のモジュラーチェッカーを実行します。
| # | チェッカーモジュール | 抽出される主な AstFacts 出力とシグナル |
|---|---|---|
| 1 | インポート衛生 (imports.py) |
import_hygiene_coverage_pct、unsorted_imports、wildcard_imports、関数内 late_imports |
| 2 | 型安全性 (typing.py) |
type_safety_coverage_pct、引数/戻り値/フィールド型注釈の欠落(type_safety_issues)、Any/raw-generic/mixed/interface{} の使用(weak_type_usages)、および unscoped_type_ignores(# type: ignore, @SuppressWarnings, @ts-ignore) |
| 3 | API ドキュメント (docs.py) |
PEP 257、Javadoc、PHPDoc、GoDoc、JSDoc/TSDoc にわたる docstring_coverage_pct、missing_docstrings、empty_docstrings、引数/戻り値の doc_signature_mismatches |
| 4 | コード品質 & インベントリ (quality.py) |
正確な total_files, total_loc, total_classes, total_functions、ファイル別 module_inventory(public_types, public_callables)、complex_functions(> 50 行、> 7 分岐、または認知的複雑度 > 15)、excessive_parameters(> 6)、global_state_usages、bug_risk_idioms |
| 5 | 依存関係 & レイヤー境界 (dependencies.py) |
internal_import_graph、Tarjan SCC circular_dependencies、型チェック専用の type_checking_cycles、unused_dependencies、undeclared_dependencies、アーキテクチャ boundary_violations |
| 6 | テストスイート健全性 (testing.py) |
test_functions_count、module_test_coverage_pct、assertion_free_tests、およびフィクスチャ/ハーネス検出(conftest.py, JUnit 5, PHPUnit, testing.T, Vitest/Jest) |
| 7 | フレームワーク & イディオム (frameworks.py) |
FastAPI/Flask/Django、Spring Boot、Laravel/WordPress、Gin/Echo/net/http、Express/NestJS/Next.js のフレームワーク固有アンチパターン(framework_issues)と modernization_hints |
| 8 | 運用性 & 12-Factor (operational.py) |
missing_io_timeouts(タイムアウト未指定の HTTP/DB/RPC 呼び出し)、scattered_env_access(設定モジュール外の os.getenv / System.getenv / process.env)、unstructured_logging、AST レベルの hardcoded_secrets |
| 9 | セキュリティシンク & ルート (security.py) |
CWE 分類付き dangerous_sinks(sql_injection, xss, command_injection, unsafe_deserialization, code_execution, path_traversal)および抽出された HTTP ルート(api_endpoints: http_method, path, handler, auth_mechanisms) |
1.3 言語バージョン互換性チェックと pni.toml 除外フィルタリング
9 個の AST チェッカーと並行して、run_ast_analyze() は 2 つの横断的な決定論的パスを実行します。
- マニフェスト対構文の互換性検証 (
version_compat.py): パースされた CST 構文をマニフェストで宣言された最小ランタイムバージョン(requires-python、Maven/Gradle Java ターゲット、composer.jsonPHP 制約、またはgo.modのgoディレクティブ)と照合します。下限バージョン未満での Python 3.10+match/caseや 3.12+type文、Java 8 ターゲットでの Java 16+recordや Java 11+String.isBlank()/java.net.http、PHP 7.x 制約での PHP 8.0+match/?->や 8.1+enum/readonly、古いgoディレクティブでの Go 1.18+ ジェネリクスや Go 1.21+/1.23+ 標準パッケージ(log/slog,slices,iter)をlanguage_version_mismatchesとして検出します。 - 監査証跡付き
[[opt_out]]除外ルール (pni.toml):pni.tomlの明示的な[[opt_out]]ルールに対してCodeLocationおよびSecuritySinkLocationをフィルタリングしつつ、除外された件数(opt_out_match_counts)を正確に記録して監査の透明性を維持します。
2. 隔離コンテナサンドボックス: ランタイム検証、Trivy、ライブ侵入テスト
ホスト環境を汚染せず外部ネットワークへの誤送信を防ぐため、pni501 は run_check、security_scan、および web_pentest を隔離された Docker または Podman コンテナ内(engine_detect.py が rootless Podman または Docker を自動検出)で実行します。
Source Code
from drawlib.canvas import save, setup
from drawlib.diagrams.architecture import ArchitectureDiagram, Node, NodeGroup, PhosphorIcon
from drawlib.styles import Styles
setup(width=176, height=96, dpi=150)
diag = ArchitectureDiagram(
node_style=Styles.PrimaryBold,
node_text_style=Styles.DarkBold.patch(text_size=10.0),
edge_style=Styles.DarkBold,
edge_text_style=Styles.DarkBold.patch(text_size=10.0),
node_card_style=Styles.WhiteBordered.patch(shape_r=1.5),
)
g1 = diag.add(
NodeGroup(
title="1. run_check (Offline Sandbox)",
width=53.0,
height=88.0,
style=Styles.Neutral.patch(shape_r=2.0),
text_style=Styles.PrimaryBold.patch(text_size=10.5, halign="left", valign="top"),
),
xy=(3.0, 4.0),
)
r1 = g1.add(
Node(
"selector.py\nMaterialize .pni/dockerfiles/\n(6-Lang Build Harness)",
width=45.0,
height=18.5,
icon=PhosphorIcon.PACKAGE,
icon_size=5.2,
),
xy=(26.5, 65.5),
)
r2 = g1.add(
Node(
"Offline Test Runner\n--network=none\nJUnit XML + Line/Branch Cov",
width=45.0,
height=19.0,
icon=PhosphorIcon.TERMINAL_WINDOW,
icon_size=5.4,
style=Styles.WhiteBold,
text_style=Styles.WhiteBold.patch(text_size=10.0),
card_style=Styles.PrimaryFlat.patch(shape_r=1.5),
),
xy=(26.5, 40.5),
)
r3 = g1.add(
Node(
"smoke.py Boot Probe\nlibrary / cli / web_api\n-> RuntimeFacts JSON",
width=45.0,
height=18.5,
icon=PhosphorIcon.PULSE,
icon_size=5.2,
card_style=Styles.PrimaryNeutral.patch(shape_r=1.5),
),
xy=(26.5, 15.5),
)
g2 = diag.add(
NodeGroup(
title="2. security_scan (Socketless Trivy)",
width=53.0,
height=88.0,
style=Styles.Neutral.patch(shape_r=2.0),
text_style=Styles.SecondaryBold.patch(text_size=10.5, halign="left", valign="top"),
),
xy=(61.5, 4.0),
)
s1 = g2.add(
Node(
"Dual-Surface Target\nRepo Filesystem +\ndocker/podman save Image Tar",
width=45.0,
height=18.5,
icon=PhosphorIcon.HARD_DRIVES,
icon_size=5.2,
),
xy=(26.5, 65.5),
)
s2 = g2.add(
Node(
"Aqua Trivy Container\nNo docker.sock Mount\nShared .pni/cache/trivy-cache",
width=45.0,
height=19.0,
icon=PhosphorIcon.SHIELD_WARNING,
icon_size=5.4,
card_style=Styles.SecondaryNeutral.patch(shape_r=1.5),
),
xy=(26.5, 40.5),
)
s3 = g2.add(
Node(
"App vs OS CVE Split\nIaC Misconfigs & Redacted\nSecrets -> SecurityFacts",
width=45.0,
height=18.5,
icon=PhosphorIcon.LOCK_KEY,
icon_size=5.2,
card_style=Styles.PrimaryNeutral.patch(shape_r=1.5),
),
xy=(26.5, 15.5),
)
g3 = diag.add(
NodeGroup(
title="3. web_pentest (Loopback Probe)",
width=53.0,
height=88.0,
style=Styles.Neutral.patch(shape_r=2.0),
text_style=Styles.DangerBold.patch(text_size=10.5, halign="left", valign="top"),
),
xy=(120.0, 4.0),
)
p1 = g3.add(
Node(
"server_plan.py\nBoot Target Web Container\nBound to 127.0.0.1:<port>",
width=45.0,
height=18.5,
icon=PhosphorIcon.GLOBE,
icon_size=5.2,
),
xy=(26.5, 65.5),
)
p2 = g3.add(
Node(
".pni/pentests/<target>/\npytest + httpx Suite\n(Baseline + AST Route Probes)",
width=45.0,
height=19.0,
icon=PhosphorIcon.BUG,
icon_size=5.4,
style=Styles.DangerBold,
card_style=Styles.DangerNeutral.patch(shape_r=1.5),
),
xy=(26.5, 40.5),
)
p3 = g3.add(
Node(
"Clean Teardown & Parse\nCWE-Tagged Findings\n-> PentestFacts JSON",
width=45.0,
height=18.5,
icon=PhosphorIcon.FILE_TEXT,
icon_size=5.2,
style=Styles.SuccessBold,
card_style=Styles.SuccessNeutral.patch(shape_r=1.5),
),
xy=(26.5, 15.5),
)
diag.connect(r1, r2, routing="orthogonal")
diag.connect(r2, r3, routing="orthogonal")
diag.connect(r1, s1, routing="orthogonal", style=Styles.PrimaryBold)
diag.connect(s1, s2, routing="orthogonal")
diag.connect(s2, s3, routing="orthogonal")
diag.connect(s1, p1, routing="orthogonal", style=Styles.PrimaryBold)
diag.connect(p1, p2, routing="orthogonal", style=Styles.DangerBold)
diag.connect(p2, p3, routing="orthogonal", style=Styles.SuccessBold)
diag.draw(xy=(0.0, 0.0))
save()
2.1 隔離コンテナランタイム検証 (run_check -> RuntimeFacts)
src/pni/_actions/runtime_check/ に実装された run_runtime_check() は、隔離コンテナ内でビルド可能性、単体テスト実行、カバレッジ計測、および起動健全性を検証し、.pni/cache/<workflow_id>/runtime/runtime_facts.json に永続化します。
- ハーネス生成 & イメージビルド (
selector.py): プロジェクト構成に応じた.pni/dockerfiles/テンプレート(uv,poetry,pip,maven,gradle,composer,go,npm,pnpm,yarn,raw)を選択し、プロジェクトDockerfileの検証とテストハーネスイメージのビルドを行います(go.dockerfileにおける非 rootUSER pniと事前ウォームアップ済み/go/cache、およびnode.dockerfileにおける@playwright/test/playwright/@vitest/browser検出時の Playwright Headless Chromium 自動プロビジョニングを含む)。 - オフライン単体テスト & 実測カバレッジ (
--network=none): 外部ネットワーク通信を決定論的に遮断する--network=noneでテストスイートを実行し、JUnit XML(TestExecutionResult+FailedTestCase抜粋)や Node テストランナー出力(@web/test-runner, Mocha, Vitest, Jest)を解析するとともに、pytest-covJSON、JaCoCo XML、Clover XML、go test -coverprofile、または Vitest/Jest のcoverage-summary.json/lcov.infoレポートから行/分岐カバレッジ(CoverageResult+lowest_covered_modules)を抽出します。 - アーキタイプ別スモークプローブ (
smoke.py):runtime_typesに応じたコンテナ起動プローブを実行し(library: クリーンインポート検証、cli:--help終了コード0検証、cli+library: 両方の複合検証、web_api: 起動 + ループバック HTTP 疎通検証)、失敗要因をBootFailureKindに分類します。
2.2 ソケットレスコンテナ脆弱性 & シークレットスキャン (security_scan -> SecurityFacts)
src/pni/_actions/security_scan/ に実装された run_security_scan() は、共有脆弱性 DB キャッシュ(.pni/cache/trivy-cache/)をマウントしたソケットレスコンテナ内で Aqua Trivy(ghcr.io/aquasecurity/trivy)を実行し、.pni/cache/<workflow_id>/security/security_facts.json に永続化します。
docker.sock不要の二面スキャン: ホストの/var/run/docker.sockを一切マウントせず、リポジトリファイルシステム(scan_filesystem)とdocker save/podman saveでエクスポートした解決済みコンテナイメージ tar(scan_resolved_image)の両方をスキャンします。- 4段階の脆弱性スコープ分類(
runtime_core,dev_or_optional,os_package,harness_os)とマスク済みシークレット: 重複排除された CVE をapp_vuln_countsとos_vuln_countsに分離し、dep_scopes.pyによってpyproject.toml/uv.lock、pom.xml、composer.json、およびモノレポの Node ロックファイル(pnpm-lock.yaml,package-lock.json,yarn.lock)の依存スコープやコンテナ内グローバルツールチェーンパス(/usr/local/lib/node_modules等)を正確に分類した上で、IaC/Dockerfile のmisconfigurationsおよびマスク済みSecretFindingItemを抽出します。
2.3 ライブループバック Web ペネトレーションテスト (web_pentest -> PentestFacts)
src/pni/_actions/pentest/ に実装された run_web_pentest() は、Web アプリケーションに対する動的なブラックボックス HTTP セキュリティプローブを実行し、.pni/cache/<workflow_id>/pentest/pentest_facts.json に永続化します。
- フェイルクローズドな明示的プローブスイート (
.pni/pentests/<target>/):web_pentestを含むワークフローは、.pni/pentests/<target>/にconftest.pyとtest_*.pyが存在しない限りpni workflows start時にフェイルクローズドでブロックします。pni pentests init -w <workflow_id>を実行すると、ベースラインのセキュリティヘッダ・CORS・HTTP メソッド検証に加え、AstFacts.api_endpointsから自動合成されたtest_target_surface.pyルートプローブが生成されます。 - ループバックコンテナ実行 (
127.0.0.1):server_plan.pyがホストのループバック(127.0.0.1:<port>)に厳密にバインドされたコンテナ内で対象 Web サーバーを起動し、ライブエンドポイント(または明示的な--base-url)に対してpytest + httpxを実行した後、コンテナをクリーンに破棄して JUnit XML の失敗を CWE タグ付きPentestFindingItemレコードへ変換します。
# AST 検出ルートから .pni/pentests/<target>/ を生成し、ループバックコンテナに対して実行
pni pentests init -w <workflow_id>
pni pentests run -w <workflow_id>
3. グラフ・契約駆動マイグレーション解析器 (migration_analyze -> MigrationFacts)
src/pni/_actions/migration_analyze/ に実装された run_migration_analyze() は、完了済みの based_on 監査実行に紐づく category: migration ワークフローを支え、.pni/cache/<workflow_id>/migration_facts.json に永続化します。
- Tarjan SCC 循環切断 & トポロジカル Wave
1..4(graph_waves.py):internal_import_graph内の強連結成分(SCC)を検出し、最小限のcycle_cuts(CycleCutSuggestion)を推奨するとともに、末端の DTO/ユーティリティ(Wave 1)からドメイン/サービス層(Waves 2..3)、エントリポイント(Wave 4)までモジュールを段階分けします。 - 暗黙的契約、境界タッチポイント、および
DEC-XX候補抽出: 型付けされていないリクエスト/レスポンスキー(contracts.pyのimplicit_contracts)およびenv_contractsを復元し、in_memory_state,persistence_db,external_http_rpcの結合箇所(touchpoints.pyのboundary_touchpoints)を特定します。さらに自動コード変換ツール(codemods.pyのruff,openrewrite,rector,gofmt,jscodeshift)をマッチングし、仕様かバグか曖昧なレガシー挙動をDEC-XXspec_vs_bug_candidates(ambiguities.py)として抽出します。
続いて、これらの決定論的ファクトが 7軸品質スコアリングモデル および Drawlib ビジュアルレポーター にどのように統合されるかを確認するか、対応 6 言語マトリクス で各言語の解析詳細を参照してください。