fix(terraform): create edges to outputs of modules expanded by count or for_each - #7663
Open
AlexKantor87 wants to merge 2 commits into
Open
fix(terraform): create edges to outputs of modules expanded by count or for_each#7663AlexKantor87 wants to merge 2 commits into
AlexKantor87 wants to merge 2 commits into
Conversation
…or for_each
A module block expanded by count or for_each gets vertices named "name[idx]"
or 'name["key"]', but references to its outputs never match those vertices, so
the referencing resource gets no edge to the module output and every graph
check involving the module fails regardless of the configuration. For example,
a security group created in a module called with count and attached via
module.sg[0].security_group_id fails CKV2_AWS_5 even though the attachment is
right there in the same graph. Plain module calls, including nested ones, are
unaffected, which is why this has been easy to miss: it only bites codebases
that use count as their conditional-module idiom, and for those it bites on
every graph check at once.
Three parts to the fix:
1. _get_possible_vertices misses expanded module vertices because references
are index-stripped before lookup while the vertices keep their suffix. A
fallback now matches on the suffix-stripped vertex name (numeric and string
keys both).
2. A for_each string key survives tokenisation ('sg["alpha"]' arrives intact),
but the output selection in _connect_module took the first admissible output
vertex and stopped, so which instance a reference bound to depended on
iteration order and flipped between runs. Expanded module instances now
connect to their own output, matched on TFModule.foreach_idx.
3. A numeric count index does NOT survive tokenisation:
module.sg[1].security_group_id reaches edge building as ['sg',
'security_group_id'], so per-instance binding is impossible there and
best-match silently orphaned every instance it did not pick. References that
resolve by name alone now connect to every expanded instance of that module
block. The instances are expansions of a single configuration block, so this
keeps the graph a faithful superset instead of dropping edges.
Known limitations, deliberately out of scope because they sit in reference
tokenisation rather than edge building: splat (module.sg[*].attr loses both the
marker and the attribute, arriving as ['sg']), a dynamic index
(module.sg[var.i].attr, same), and a for_each key containing a dot
(module.sg["a.b"].attr is split inside the quotes into ['sg["a', 'b"]', ...]).
Three tests: the first fails on main with 'expected to find edge from [resource
aws_lb.alb] to [output security_group_id]' and passes with the fix; a count = 2
fixture asserts every instance's output gains an incoming edge; a for_each
fixture asserts each reference binds to exactly its own instance. The full
tests/terraform/graph tree passes either way, apart from two pre-existing
rustworkx solver failures that are identical with and without this change.
Found on 3.3.6, still present on 3.3.15 and main.
AlexKantor87
requested a deployment
to
scan-security
August 26, 2026 14:38 — with
GitHub Actions
Waiting
AlexKantor87
requested a deployment
to
scan-security
August 27, 2026 05:01 — with
GitHub Actions
Waiting
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
Description
A module block expanded by
countorfor_eachgets vertices namedname[idx]/name["key"], but references to its outputs never match those vertices, so the referencing resource gets no edge to the module output and every graph check involving the module fails regardless of the configuration. For example, a security group created in a module called withcount = var.create_sg ? 1 : 0and attached viamodule.sg[0].security_group_idfailsCKV2_AWS_5even though the attachment is right there in the same graph. Plain module calls, including nested ones, are unaffected, which I suspect is why this has gone unnoticed: it only bites codebases that usecountas their conditional-module idiom, and for those it bites on every graph check at once.Three parts to the fix:
_get_possible_verticesmisses expanded module vertices because references are index-stripped before lookup while the vertices keep their suffix. A fallback now matches on the suffix-stripped vertex name, covering numeric and string keys both.for_eachinstance binding. A string key survives tokenisation (sg["alpha"]arrives intact), but_connect_moduletook the first admissible output vertex and stopped, so which instance a reference bound to depended on iteration order and flipped between runs. Expanded instances now connect to their own output, matched onTFModule.foreach_idx.countinstance binding. A numeric index does not survive tokenisation:module.sg[1].security_group_idreaches edge building as['sg', 'security_group_id'], so per-instance binding is impossible there, and best-match silently orphaned every instance it did not pick. References that resolve by name alone now connect to every expanded instance of that module block. The instances are expansions of a single configuration block, so this keeps the graph a faithful superset instead of dropping edges.Verified shapes
countmodule, indexed referencecount = 2, one consumer per instancefor_each, string-key referencestry()network_configuration)Known limitations, deliberately out of scope
These sit in reference tokenisation (
get_referenced_vertices_in_value), a layer above edge building, and I did not want to widen a targeted fix into that:module.sg[*].attrloses both the marker and the attribute, arriving as['sg']module.sg[var.i].attr, samefor_eachkey containing a dot:module.sg["a.b"].attris split inside the quotes into['sg["a', 'b"]', ...]Happy to raise these as separate issues with reproductions if useful.
Related in spirit to #6145, which fixed the same class of problem for the
terraform_planframework. Found on 3.3.6, still present on 3.3.15 and main.Checklist:
Three tests: the first fails on main with
expected to find edge from [resource aws_lb.alb] to [output security_group_id]and passes with the fix; acount = 2fixture asserts every instance's output gains an incoming edge; afor_eachfixture asserts each reference binds to exactly its own instance. The fulltests/terraform/graphtree passes either way, apart from two pre-existing rustworkx solver failures that are identical with and without this change.