Releases: oocx/tfplan2md
Release list
v1.45.2
Plan status / drift regressions fixed (Issue 661)
Two regressions in plan status rendering have been fixed. Scope is limited to the plan-status banner and drift-section filtering for no-change plans.
🐛 Bug fixes
-
False "Plan is not applyable" warning suppressed for no-change plans.
When Terraform produces an effective no-op plan (no resource changes, no drift), theapplyable = falseflag in the plan JSON could trigger a⛔ Plan is not applyablewarning banner even though the plan posed no real problem. The condition now only fires the banner when the plan is also either errored or genuinely actionable (has changes or drift), matching what practitioners actually need to act on. -
Drift section no longer emits noise entries for no-op drift.
For plans that recorded drift metadata but where every drift entry was a no-op (unchanged resources), the drift section previously rendered empty or no-op blocks. The fix applies the same display-filtering semantics already used for normal resource changes: no-op and fully suppressed drift entries are hidden, keeping the report free of noise for effectively clean baselines.
📸 Screenshots
No false warning — no-change plan renders cleanly
Warning correctly shown for actionable non-applyable plans
🔗 Commits
f344afbfix: suppress non-applyable banner and drift noise for no-change/no-drift baselines
v1.45.1
v1.45.0
Inline relevant attributes
tfplan2md now renders Terraform plan context inline on the affected resource cards instead of isolating relevant_attributes[] at the bottom of the report. This makes replacement cascades and upstream dependencies visible where reviewers make decisions.
✨ Features
- Replaced or destroyed resources now show inline
⚠️ Forced replacementcallouts when areplace_pathsentry traces back to an upstream relevant attribute. - Replaced or destroyed resources also show inline
🔗 Depends on:/🔗 Also depends on:context for correlated upstream inputs. - Upstream inputs that are themselves changing in the same plan are highlighted as changing in this plan.
- Uncorrelated plan inputs are preserved in a collapsible
🔗 Other plan inputssection near the end of the report.
📚 Documentation
- Updated
docs/features.mdto describe the new inline relevant-attribute annotations and fallback details block.
📸 Screenshots
Inline annotations
Fallback inputs
🔗 Commits
1dc93917feat: implement inline relevant attributes (Tasks 1-7, partial - build fixes in progress)
🚨 Breaking changes
None.
v1.44.0
Multi-Platform Docker Image Build (linux/amd64 + linux/arm64)
The official Docker image is now published as a multi-platform manifest supporting both linux/amd64 and linux/arm64. Pulls on ARM64 hosts (e.g., Apple Silicon, AWS Graviton, Raspberry Pi) will automatically receive a native binary without any extra flags.
✨ Features
- ARM64 Docker image support. The
ghcr.io/oocx/tfplan2mdimage now includes a nativelinux/arm64layer alongside the existinglinux/amd64layer. Docker automatically selects the right layer based on the host architecture.
🔗 Commits
cc4de0cffeat: enable multi-platform Docker image build (linux/amd64, linux/arm64)
v1.43.3
Security Fixes: CodeQL Bad HTML Filtering Regexp and Supply-Chain Pinning
This patch resolves five GitHub security alerts — three CodeQL high-severity findings and two OpenSSF Scorecard supply-chain findings — with no change to any user-facing behaviour.
🔒 Security fixes
Fixed incomplete HTML comment regex in embedded highlight.js (Alerts #42, #43, #44)
Problem: CodeQL rule js/bad-html-filtering-regexp (CWE-185) flagged two regex patterns inside the minified highlight.js 11.9.0 bundle that is embedded inline in all three HTML wrapper templates:
e.COMMENT(/<!--/, /-->/)— the end pattern/-->/does not match the alternative HTML5 comment terminator--!>, making it an incomplete HTML comment filter./<![a-zA-Z]/— flagged as a potential overly-broad tag filter.
Fix: Patched both patterns directly in all three templates (a minimal inline fix rather than a full library upgrade):
/-->/→/-->|--!>/— now handles both valid HTML5 comment terminators./<![a-zA-Z]/→/<![A-Z]/— XML/SGML declarations (<!DOCTYPE,<!ELEMENT, etc.) always use uppercase; this change is semantically equivalent given that highlight.js compiles all patterns with thei(case-insensitive) flag for HTML/XML mode.
Templates updated:
src/tools/Oocx.TfPlan2Md.HtmlRenderer/templates/github-wrapper.htmlsrc/tools/Oocx.TfPlan2Md.HtmlRenderer/templates/github-wrapper-light.htmlsrc/tools/Oocx.TfPlan2Md.HtmlRenderer/templates/azdo-wrapper.html
Pinned SLSA generator reusable workflow to immutable SHA (Alert #113)
Problem: The SLSA provenance generation step in .github/workflows/release.yml referenced the slsa-framework/slsa-github-generator reusable workflow by the mutable tag @v2.1.0. If the tag were moved to a different commit (intentionally or through a supply-chain attack), the release pipeline would silently run different code.
Fix: Replaced the mutable tag reference with the immutable commit SHA for v2.1.0:
# Before
uses: slsa-framework/slsa-github-generator/...@v2.1.0
# After
uses: slsa-framework/slsa-github-generator/...@f7dd8c54c2067bafc12ca7a55595d5ee9b75204a # v2.1.0Pinned Alpine package versions in Dockerfile (Alert #99)
Problem: src/Dockerfile installed seven Alpine packages (upx, clang, build-base, zlib-dev, linux-headers, bash, lld) without version constraints. While the base image was already digest-pinned, the packages themselves were resolved from the Alpine package repository at build time, introducing a supply-chain risk.
Fix: All seven packages are now pinned to the exact versions present in the Alpine 3.23 package index that corresponds to the already-pinned base image digest:
RUN apk add --no-cache \
upx=5.0.2-r0 \
clang=21.1.2-r2 \
build-base=0.5-r3 \
zlib-dev=1.3.2-r0 \
linux-headers=6.16.12-r0 \
bash=5.3.3-r1 \
lld=21.1.2-r1ℹ️ Maintainer action required (not fixed by code)
Two Scorecard alerts require GitHub repository settings changes that cannot be applied via a code commit:
| Alert | Required action |
|---|---|
| #103 Code-Review (High) | Settings → Branches → main → enable "Require a pull request before merging" (≥ 1 approval) |
| #48 Branch-Protection (High) | Settings → Branches → main → enable full branch protection (status checks, dismiss stale reviews, include admins) |
🔗 Commits
v1.43.2
1.43.2 (2026-05-16)
🐛 Bug Fixes
- docker: remove --locked-mode from Dockerfile to fix ILCompiler version mismatch (0f0ff8a)
- workflow: refresh main project NuGet lock for locked-mode restore (810f1f8)
- workflow: remove explicit AOT package refs that broke docker publish (82b3249)
- workflow: stabilize locked restore across CI and Docker SDK contexts (9d54c27)
v1.43.1
Pending import no-op status fix (Issue 123)
This bug-fix release resolves a false-positive status in Terraform refactoring output. Scope is limited to import-status classification and related rendered report output.
🐛 Bug fixes
- Fixed pending
importing.id + no-opresources being mislabeled as already imported. - Pending imports now render as
✅ Readywhile keeping existing moved-resource behavior unchanged.
📸 Screenshots
After
🔗 Commits
a93d7ea0Fix false “already imported” status for pending Terraform imports
v1.43.0
Terraform 1.14 / 1.15 Plan-JSON Support
tfplan2md now renders the new fields introduced by Terraform 1.14 and 1.15 — action invocations, plan-context status, drift detection, relevant attributes, and deprecation warnings — all without any change to your CLI invocation. Plans from Terraform 1.13 and earlier are unaffected.
✨ Features
🎬 Action invocations (Terraform 1.14)
Terraform 1.14 adds action_invocations[] and deferred_action_invocations[] to the plan JSON, produced by provider-shipped Actions (e.g. aws_lambda_invoke, aws_cloudfront_create_invalidation) and by lifecycle { action_trigger { … } } blocks. These are now rendered inline under the resource that triggers them, using an #### 🎬 Actions H4 section inside each resource's collapsible block. Deferred actions are prefixed ⏳ and include a "will run on a subsequent apply" callout. Actions with no matching resource section (orphan/invoke-mode) appear in a new top-level ## 🎬 Other Actions section.
🌀 Drift detection (Terraform 1.14)
When resource_drift[] is non-empty — meaning Terraform detected that real-world state diverged from what the state file recorded — a ## 🌀 Drift Detected section now appears in the report, listing each drifted resource in the same collapsible before/after format used for resource changes.
🛑 Plan status banners (Terraform 1.14)
The plan booleans errored, applyable, and complete are now read. When any of them signals a non-ordinary state, a blockquote banner is injected immediately after the report title:
- 🛑 Plan errored — when
errored: true - ⛔ Plan is not applyable — when
applyable: false ⚠️ Plan is incomplete — whencomplete: false
All three can appear together.
📋 Relevant attributes (Terraform 1.14)
When relevant_attributes[] is present, a ## Relevant Attributes section is rendered with a two-column Resource | Attribute path table, making upstream dependencies that triggered cascading changes visible to reviewers.
⚠️ Deprecation warnings (Terraform 1.15)
Terraform 1.15 adds a deprecated string to configuration.root_module.variables[*] and configuration.root_module.outputs[*]. Any variable or output that is both deprecated and referenced by the plan now produces a warning entry in the ### Warnings section of the Code Analysis Summary, showing the name and the deprecation message from the Terraform configuration.
Backwards compatibility
All five rendering additions are strictly additive. Plans produced by Terraform 1.13 and earlier (which omit these fields) render byte-for-byte identically to the previous version.
🐛 Bug fixes
- Deprecation warnings silently dropped when no SARIF scan findings were present.
CodeAnalysisSectionRendererreturned early with "No code analysis findings were reported" whenever the SARIF total count was zero, even when deprecation warnings existed. Fix: the early-return now requires both SARIF count and warnings count to be zero; the SARIF severity table is only rendered when there are actual SARIF findings.
📸 Screenshots
See inline screenshots above for each feature area.
🔗 Commits
60aee150feat(parsing): add Terraform 1.14/1.15 action and relevant-attribute recordsd3a1765afeat(parsing): extend TerraformPlan with optional Terraform 1.14/1.15 fieldsd0f4820dfeat(parsing): add ConfigurationDeprecationReader helper3f7e96fffeat(features/122): plan-status banner, drift, relevant attrs, actions, deprecations (Tasks 5-13)e81027fdfix(feature/122): fix deprecation warnings silently dropped when no SARIF findings; add sealed to PlanStatusModel/RelevantAttributeModel
v1.42.1
v1.42.0
Summary table now shows imports/moves and hides empty rows
The Summary table at the top of every report now surfaces Terraform import and moved blocks as their own rows, hides any action row whose count is 0, and falls back to a clean "No changes" message when no row has any content. Reports stay focused on what actually changes.
✨ Enhancements
Imports and moves are now first-class summary rows
📥 Import and 🔀 Move rows are added to the Summary table whenever the plan contains resources brought in via import blocks or relocated via moved blocks. Each row uses the same Count and Resource Types breakdown as the existing action rows, so refactoring activity is visible at a glance without scrolling to the per-resource details.
## Summary
| Action | Count | Resource Types |
| -------- | ------- | ---------------- |
| ➕ Add | 2 | 1 azurerm_resource_group<br/>1 azurerm_virtual_network |
| 📥 Import | 2 | 1 azurerm_resource_group<br/>1 azurerm_storage_account |
| 🔀 Move | 1 | 1 azurerm_virtual_network |
| **Total** | **2** | |Imports and moves are reported alongside the action buckets (a single change can appear in both an action row and an Import/Move row). The Total still reflects only resource changes (add + change + replace + destroy), so the existing Azure DevOps tfplan2md_haschanges pipeline variable behavior is unchanged.
Zero-count rows are hidden
Rows whose count is 0 are no longer rendered, so a create-only plan now shows just the Add row instead of three empty Change, Replace, and Destroy rows. This keeps the summary tight and matches the new behavior for Import/Move rows (they only appear when there is something to report).
Before:
| ➕ Add | 2 | 1 azurerm_resource_group<br/>1 azurerm_storage_account |
| 🔄 Change | 0 | |
| ♻️ Replace | 0 | |
| ❌ Destroy | 0 | |
| **Total** | **2** | |After:
| ➕ Add | 2 | 1 azurerm_resource_group<br/>1 azurerm_storage_account |
| **Total** | **2** | |"No changes" replaces the empty table
When every row would be hidden — including the new Import/Move rows — the renderer keeps the existing "No changes" paragraph in place of an empty table.
📚 Documentation
docs/features.mdreplaces the old "No-Changes Summary" section with a new "Concise Summary Table" section that documents both the import/move rows and the zero-count filtering.









