Repository: tfsec/tfsec
Pull request #454 (2020-10-20 21:18:29)

Refactor parser
WIP, more info soon...


Patch:
diff --git a/README.md b/README.md
index 366fcfd9b..de83ddf2e 100644
--- a/README.md
+++ b/README.md
@@ -41,11 +41,9 @@ go get -u github.com/tfsec/tfsec/cmd/tfsec
 
 ## Usage
 
-tfsec will recursively scan the specified directory. If no directory is
-specified, the current working directory will be used.
+tfsec will scan the specified directory. If no directory is specified, the current working directory will be used.
 
-The exit status will be non zero if problems are found, otherwise the
-exit status will be zero.
+The exit status will be non-zero if tfsec finds problems, otherwise the exit status will be zero.
 
 ```bash
 tfsec .
@@ -53,8 +51,7 @@ tfsec .
 
 ## Use with Docker
 
-As an alternative to installing and running tfsec on your system, you may
-run tfsec in a Docker container.
+As an alternative to installing and running tfsec on your system, you may run tfsec in a Docker container.
 
 To run:
 
@@ -117,10 +114,6 @@ tfsec . -e GEN001,GCP001,GCP002
 
 You can include values from a tfvars file in the scan,  using, for example: `--tfvars-file terraform.tfvars`.
 
-## Excluding Directories
-
-You can exclude directories from being scanned using the `--exclude-dir [directory]` flag. This can be used multiple times to exclude multiple directories.
-
 ## Included Checks
 
 Checks are currently limited to AWS/Azure/GCP resources, but
diff --git a/cmd/tfsec/main.go b/cmd/tfsec/main.go
index f6ed047a2..835b5bbec 100644
--- a/cmd/tfsec/main.go
+++ b/cmd/tfsec/main.go
@@ -2,12 +2,13 @@ package main
 
 import (
 	"fmt"
-	"github.com/tfsec/tfsec/internal/app/tfsec/custom"
 	"os"
 	"path/filepath"
 	"runtime"
 	"strings"
 
+	"github.com/tfsec/tfsec/internal/app/tfsec/custom"
+
 	"github.com/tfsec/tfsec/internal/app/tfsec/debug"
 
 	"github.com/tfsec/tfsec/internal/app/tfsec/formatters"
@@ -27,7 +28,6 @@ var disableColours = false
 var format string
 var softFail = false
 var excludedChecks string
-var excludeDirectories []string
 var tfvarsPath string
 var outputFlag string
 
@@ -38,7 +38,6 @@ func init() {
 	rootCmd.Flags().StringVarP(&format, "format", "f", format, "Select output format: default, json, csv, checkstyle, junit")
 	rootCmd.Flags().StringVarP(&excludedChecks, "exclude", "e", excludedChecks, "Provide checks via , without space to exclude from run.")
 	rootCmd.Flags().BoolVarP(&softFail, "soft-fail", "s", softFail, "Runs checks but suppresses error code")
-	rootCmd.Flags().StringSliceVar(&excludeDirectories, "exclude-dir", []string{}, "Exclude a directory from the scan. You can use this flag multiple times to exclude further directories.")
 	rootCmd.Flags().StringVar(&tfvarsPath, "tfvars-file", tfvarsPath, "Path to .tfvars file")
 	rootCmd.Flags().StringVar(&outputFlag, "out", outputFlag, "Set output file")
 	rootCmd.Flags().BoolVar(&debug.Enabled, "verbose", debug.Enabled, "Enable verbose logging")
@@ -115,15 +114,6 @@ var rootCmd = &cobra.Command{
 			os.Exit(1)
 		}
 
-		var absoluteExcludes []string
-		for _, exclude := range excludeDirectories {
-			exDir, err := filepath.Abs(exclude)
-			if err != nil {
-				continue
-			}
-			absoluteExcludes = append(absoluteExcludes, exDir)
-		}
-
 		if tfvarsPath != "" {
 			tfvarsPath, err = filepath.Abs(tfvarsPath)
 			if err != nil {
@@ -133,7 +123,7 @@ var rootCmd = &cobra.Command{
 		}
 
 		debug.Log("Starting parser...")
-		blocks, err := parser.New().ParseDirectory(dir, absoluteExcludes, tfvarsPath)
+		blocks, err := parser.New(dir, tfvarsPath).ParseDirectory()
 		if err != nil {
 			fmt.Println(err)
 			os.Exit(1)
diff --git a/docs-website/_docs/home.md b/docs-website/_docs/home.md
index 19e418fc8..63900d885 100644
--- a/docs-website/_docs/home.md
+++ b/docs-website/_docs/home.md
@@ -27,9 +27,9 @@ go get -u github.com/tfsec/tfsec/cmd/tfsec
 
 ## Usage
 
-tfsec will recursively scan the specified directory. If no directory is specified, the current working directory will be used.
+tfsec will scan the specified directory. If no directory is specified, the current working directory will be used.
 
-The exit status will be non zero if problems are found, otherwise the exit status will be zero.
+The exit status will be non-zero if tfsec finds problems, otherwise the exit status will be zero.
 
 ```bash
 tfsec .
diff --git a/example/custom/.tfsec/custom_tfchecks.json b/example/custom/.tfsec/custom_tfchecks.json
index 32acae432..77d333135 100644
--- a/example/custom/.tfsec/custom_tfchecks.json
+++ b/example/custom/.tfsec/custom_tfchecks.json
@@ -39,7 +39,7 @@
           "value" : "true"
         }
       },
-      "errorMessage": "The the versioning block requires enabled attribute",
+      "errorMessage": "The versioning block requires enabled attribute",
       "relatedLinks": [
         "http://internal.acmecorp.com/standards/aws/tagging.html"
       ]
diff --git a/internal/app/tfsec/checks/aws001.go b/internal/app/tfsec/checks/aws001.go
index 713f5fc46..47452a421 100644
--- a/internal/app/tfsec/checks/aws001.go
+++ b/internal/app/tfsec/checks/aws001.go
@@ -51,7 +51,7 @@ func init() {
 				if acl == "public-read" || acl == "public-read-write" || acl == "website" {
 					return []scanner.Result{
 						check.NewResultWithValueAnnotation(
-							fmt.Sprintf("Resource '%s' has an ACL which allows public access.", block.Name()),
+							fmt.Sprintf("Resource '%s' has an ACL which allows public access.", block.FullName()),
 							attr.Range(),
 							attr,
 							scanner.SeverityWarning,
@@ -61,7 +61,7 @@ func init() {
 				if acl == "authenticated-read" {
 					return []scanner.Result{
 						check.NewResultWithValueAnnotation(
-							fmt.Sprintf("Resource '%s' has an ACL which allows access to any authenticated AWS user, not just users within the target account.", block.Name()),
+							fmt.Sprintf("Resource '%s' has an ACL which allows access to any authenticated AWS user, not just users within the target account.", block.FullName()),
 							attr.Range(),
 							attr,
 							scanner.SeverityWarning,
diff --git a/internal/app/tfsec/checks/aws002.go b/internal/app/tfsec/checks/aws002.go
index 93c5fb492..2a4f27d97 100644
--- a/internal/app/tfsec/checks/aws002.go
+++ b/internal/app/tfsec/checks/aws002.go
@@ -46,7 +46,7 @@ func init() {
 			if loggingBlock := block.GetBlock("logging"); loggingBlock == nil {
 				return []scanner.Result{
 					check.NewResult(
-						fmt.Sprintf("Resource '%s' does not have logging enabled.", block.Name()),
+						fmt.Sprintf("Resource '%s' does not have logging enabled.", block.FullName()),
 						block.Range(),
 						scanner.SeverityError,
 					),
diff --git a/internal/app/tfsec/checks/aws003.go b/internal/app/tfsec/checks/aws003.go
index cda668a99..8b6944e26 100644
--- a/internal/app/tfsec/checks/aws003.go
+++ b/internal/app/tfsec/checks/aws003.go
@@ -43,7 +43,7 @@ func init() {
 		CheckFunc: func(check *scanner.Check, block *parser.Block, _ *scanner.Context) []scanner.Result {
 			return []scanner.Result{
 				check.NewResult(
-					fmt.Sprintf("Resource '%s' uses EC2 Classic. Use a VPC instead.", block.Name()),
+					fmt.Sprintf("Resource '%s' uses EC2 Classic. Use a VPC instead.", block.FullName()),
 					block.Range(),
 					scanner.SeverityError,
 				),
diff --git a/internal/app/tfsec/checks/aws004.go b/internal/app/tfsec/checks/aws004.go
index 3b83aa3a4..e804b8698 100644
--- a/internal/app/tfsec/checks/aws004.go
+++ b/internal/app/tfsec/checks/aws004.go
@@ -64,7 +64,7 @@ func init() {
 				}
 				return []scanner.Result{
 					check.NewResultWithValueAnnotation(
-						fmt.Sprintf("Resource '%s' uses plain HTTP instead of HTTPS.", block.Name()),
+						fmt.Sprintf("Resource '%s' uses plain HTTP instead of HTTPS.", block.FullName()),
 						reportRange,
 						protocolAttr,
 						scanner.SeverityError,
diff --git a/internal/app/tfsec/checks/aws005.go b/internal/app/tfsec/checks/aws005.go
index 8d93dd1a3..0526b84a0 100644
--- a/internal/app/tfsec/checks/aws005.go
+++ b/internal/app/tfsec/checks/aws005.go
@@ -45,7 +45,7 @@ func init() {
 			if internalAttr := block.GetAttribute("internal"); internalAttr == nil {
 				return []scanner.Result{
 					check.NewResult(
-						fmt.Sprintf("Resource '%s' is exposed publicly.", block.Name()),
+						fmt.Sprintf("Resource '%s' is exposed publicly.", block.FullName()),
 						block.Range(),
 						scanner.SeverityWarning,
 					),
@@ -53,7 +53,7 @@ func init() {
 			} else if internalAttr.Type() == cty.Bool && internalAttr.Value().False() {
 				return []scanner.Result{
 					check.NewResultWithValueAnnotation(
-						fmt.Sprintf("Resource '%s' is exposed publicly.", block.Name()),
+						fmt.Sprintf("Resource '%s' is exposed publicly.", block.FullName()),
 						internalAttr.Range(),
 						internalAttr,
 						scanner.SeverityWarning,
diff --git a/internal/app/tfsec/checks/aws006.go b/internal/app/tfsec/checks/aws006.go
index cf80560b1..541dbe9c9 100644
--- a/internal/app/tfsec/checks/aws006.go
+++ b/internal/app/tfsec/checks/aws006.go
@@ -62,10 +62,15 @@ func init() {
 				}
 
 				for _, cidr := range cidrBlocksAttr.Value().AsValueSlice() {
+
+					if cidr.Type() != cty.String {
+						continue
+					}
+
 					if strings.HasSuffix(cidr.AsString(), "/0") {
 						return []scanner.Result{
 							check.NewResult(
-								fmt.Sprintf("Resource '%s' defines a fully open ingress security group rule.", block.Name()),
+								fmt.Sprintf("Resource '%s' defines a fully open ingress security group rule.", block.FullName()),
 								cidrBlocksAttr.Range(),
 								scanner.SeverityWarning,
 							),
@@ -82,10 +87,15 @@ func init() {
 				}
 
 				for _, cidr := range ipv6CidrBlocksAttr.Value().AsValueSlice() {
+
+					if cidr.Type() != cty.String {
+						continue
+					}
+
 					if strings.HasSuffix(cidr.AsString(), "/0") {
 						return []scanner.Result{
 							check.NewResultWithValueAnnotation(
-								fmt.Sprintf("Resource '%s' defines a fully open ingress security group rule.", block.Name()),
+								fmt.Sprintf("Resource '%s' defines a fully open ingress security group rule.", block.FullName()),
 								ipv6CidrBlocksAttr.Range(),
 								ipv6CidrBlocksAttr,
 								scanner.SeverityWarning,
diff --git a/internal/app/tfsec/checks/aws007.go b/internal/app/tfsec/checks/aws007.go
index 193d45ce0..cf10b6f16 100644
--- a/internal/app/tfsec/checks/aws007.go
+++ b/internal/app/tfsec/checks/aws007.go
@@ -61,10 +61,13 @@ func init() {
 				}
 
 				for _, cidr := range cidrBlocksAttr.Value().AsValueSlice() {
+					if cidr.Type() != cty.String {
+						continue
+					}
 					if strings.HasSuffix(cidr.AsString(), "/0") {
 						return []scanner.Result{
 							check.NewResultWithValueAnnotation(
-								fmt.Sprintf("Resource '%s' defines a fully open egress security group rule.", block.Name()),
+								fmt.Sprintf("Resource '%s' defines a fully open egress security group rule.", block.FullName()),
 								cidrBlocksAttr.Range(),
 								cidrBlocksAttr,
 								scanner.SeverityWarning,
@@ -82,10 +85,13 @@ func init() {
 				}
 
 				for _, cidr := range ipv6CidrBlocksAttr.Value().AsValueSlice() {
+					if cidr.Type() != cty.String {
+						continue
+					}
 					if strings.HasSuffix(cidr.AsString(), "/0") {
 						return []scanner.Result{
 							check.NewResultWithValueAnnotation(
-								fmt.Sprintf("Resource '%s' defines a fully open egress security group rule.", block.Name()),
+								fmt.Sprintf("Resource '%s' defines a fully open egress security group rule.", block.FullName()),
 								ipv6CidrBlocksAttr.Range(),
 								ipv6CidrBlocksAttr,
 								scanner.SeverityWarning,
diff --git a/internal/app/tfsec/checks/aws008.go b/internal/app/tfsec/checks/aws008.go
index 2b57d9cbd..7c2f5949b 100644
--- a/internal/app/tfsec/checks/aws008.go
+++ b/internal/app/tfsec/checks/aws008.go
@@ -4,6 +4,8 @@ import (
 	"fmt"
 	"strings"
 
+	"github.com/zclconf/go-cty/cty"
+
 	"github.com/tfsec/tfsec/internal/app/tfsec/scanner"
 
 	"github.com/tfsec/tfsec/internal/app/tfsec/parser"
@@ -56,10 +58,13 @@ func init() {
 					}
 
 					for _, cidr := range cidrBlocksAttr.Value().AsValueSlice() {
+						if cidr.Type() != cty.String {
+							continue
+						}
 						if strings.HasSuffix(cidr.AsString(), "/0") {
 							results = append(results,
 								check.NewResult(
-									fmt.Sprintf("Resource '%s' defines a fully open ingress security group.", block.Name()),
+									fmt.Sprintf("Resource '%s' defines a fully open ingress security group.", block.FullName()),
 									cidrBlocksAttr.Range(),
 									scanner.SeverityWarning,
 								),
@@ -77,7 +82,7 @@ func init() {
 						if strings.HasSuffix(cidr.AsString(), "/0") {
 							results = append(results,
 								check.NewResult(
-									fmt.Sprintf("Resource '%s' defines a fully open ingress security group.", block.Name()),
+									fmt.Sprintf("Resource '%s' defines a fully open ingress security group.", block.FullName()),
 									cidrBlocksAttr.Range(),
 									scanner.SeverityWarning,
 								),
diff --git a/internal/app/tfsec/checks/aws009.go b/internal/app/tfsec/checks/aws009.go
index d954d2bfb..e4f75083d 100644
--- a/internal/app/tfsec/checks/aws009.go
+++ b/internal/app/tfsec/checks/aws009.go
@@ -58,7 +58,7 @@ func init() {
 						if strings.HasSuffix(cidr.AsString(), "/0") {
 							results = append(results,
 								check.NewResultWithValueAnnotation(
-									fmt.Sprintf("Resource '%s' defines a fully open egress security group.", block.Name()),
+									fmt.Sprintf("Resource '%s' defines a fully open egress security group.", block.FullName()),
 									cidrBlocksAttr.Range(),
 									cidrBlocksAttr,
 									scanner.SeverityWarning,
@@ -77,7 +77,7 @@ func init() {
 						if strings.HasSuffix(cidr.AsString(), "/0") {
 							results = append(results,
 								check.NewResultWithValueAnnotation(
-									fmt.Sprintf("Resource '%s' defines a fully open egress security group.", block.Name()),
+									fmt.Sprintf("Resource '%s' defines a fully open egress security group.", block.FullName()),
 									cidrBlocksAttr.Range(),
 									cidrBlocksAttr,
 									scanner.SeverityWarning,
diff --git a/internal/app/tfsec/checks/aws010.go b/internal/app/tfsec/checks/aws010.go
index 97aaf3719..070dcd6e0 100644
--- a/internal/app/tfsec/checks/aws010.go
+++ b/internal/app/tfsec/checks/aws010.go
@@ -58,7 +58,7 @@ func init() {
 					if policy == sslPolicyAttr.Value().AsString() {
 						return []scanner.Result{
 							check.NewResultWithValueAnnotation(
-								fmt.Sprintf("Resource '%s' is using an outdated SSL policy.", block.Name()),
+								fmt.Sprintf("Resource '%s' is using an outdated SSL policy.", block.FullName()),
 								sslPolicyAttr.Range(),
 								sslPolicyAttr,
 								scanner.SeverityError,
diff --git a/internal/app/tfsec/checks/aws011.go b/internal/app/tfsec/checks/aws011.go
index 8b4edb5cd..ebc08bf1d 100644
--- a/internal/app/tfsec/checks/aws011.go
+++ b/internal/app/tfsec/checks/aws011.go
@@ -47,7 +47,7 @@ func init() {
 				if publicAttr.Value().True() {
 					return []scanner.Result{
 						check.NewResultWithValueAnnotation(
-							fmt.Sprintf("Resource '%s' is exposed publicly.", block.Name()),
+							fmt.Sprintf("Resource '%s' is exposed publicly.", block.FullName()),
 							publicAttr.Range(),
 							publicAttr,
 							scanner.SeverityWarning,
diff --git a/internal/app/tfsec/checks/aws012.go b/internal/app/tfsec/checks/aws012.go
index 62bfcb091..0d4d122e0 100644
--- a/internal/app/tfsec/checks/aws012.go
+++ b/internal/app/tfsec/checks/aws012.go
@@ -45,7 +45,7 @@ func init() {
 				if publicAttr.Value().True() {
 					return []scanner.Result{
 						check.NewResultWithValueAnnotation(
-							fmt.Sprintf("Resource '%s' has a public IP address associated.", block.Name()),
+							fmt.Sprintf("Resource '%s' has a public IP address associated.", block.FullName()),
 							publicAttr.Range(),
 							publicAttr,
 							scanner.SeverityError,
diff --git a/internal/app/tfsec/checks/aws013.go b/internal/app/tfsec/checks/aws013.go
index 6436cff95..a22684c9b 100644
--- a/internal/app/tfsec/checks/aws013.go
+++ b/internal/app/tfsec/checks/aws013.go
@@ -92,7 +92,7 @@ func init() {
 					for _, env := range definition.EnvVars {
 						if security.IsSensitiveAttribute(env.Name) && env.Value != "" {
 							results = append(results, check.NewResultWithValueAnnotation(
-								fmt.Sprintf("Resource '%s' includes a potentially sensitive environment variable '%s' in the container definition.", block.Name(), env.Name),
+								fmt.Sprintf("Resource '%s' includes a potentially sensitive environment variable '%s' in the container definition.", block.FullName(), env.Name),
 								definitionsAttr.Range(),
 								definitionsAttr,
 								scanner.SeverityWarning,
diff --git a/internal/app/tfsec/checks/aws014.go b/internal/app/tfsec/checks/aws014.go
index d18881266..63dca6624 100644
--- a/internal/app/tfsec/checks/aws014.go
+++ b/internal/app/tfsec/checks/aws014.go
@@ -60,7 +60,7 @@ func init() {
 			if rootDeviceBlock == nil && !encryptionByDefault {
 				results = append(results,
 					check.NewResult(
-						fmt.Sprintf("Resource '%s' uses an unencrypted root EBS block device. Consider adding <blue>root_block_device{ encrypted = true }</blue>", block.Name()),
+						fmt.Sprintf("Resource '%s' uses an unencrypted root EBS block device. Consider adding <blue>root_block_device{ encrypted = true }</blue>", block.FullName()),
 						block.Range(),
 						scanner.SeverityError,
 					),
@@ -70,7 +70,7 @@ func init() {
 				if encryptedAttr == nil && !encryptionByDefault {
 					results = append(results,
 						check.NewResult(
-							fmt.Sprintf("Resource '%s' uses an unencrypted root EBS block device. Consider adding <blue>encrypted = true</blue>", block.Name()),
+							fmt.Sprintf("Resource '%s' uses an unencrypted root EBS block device. Consider adding <blue>encrypted = true</blue>", block.FullName()),
 							rootDeviceBlock.Range(),
 							scanner.SeverityError,
 						),
@@ -78,7 +78,7 @@ func init() {
 				} else if encryptedAttr != nil && encryptedAttr.Type() == cty.Bool && encryptedAttr.Value().False() {
 					results = append(results,
 						check.NewResultWithValueAnnotation(
-							fmt.Sprintf("Resource '%s' uses an unencrypted root EBS block device.", block.Name()),
+							fmt.Sprintf("Resource '%s' uses an unencrypted root EBS block device.", block.FullName()),
 							encryptedAttr.Range(),
 							encryptedAttr,
 							scanner.SeverityError,
@@ -93,7 +93,7 @@ func init() {
 				if encryptedAttr == nil && !encryptionByDefault {
 					results = append(results,
 						check.NewResult(
-							fmt.Sprintf("Resource '%s' uses an unencrypted EBS block device. Consider adding <blue>encrypted = true</blue>", block.Name()),
+							fmt.Sprintf("Resource '%s' uses an unencrypted EBS block device. Consider adding <blue>encrypted = true</blue>", block.FullName()),
 							ebsDeviceBlock.Range(),
 							scanner.SeverityError,
 						),
@@ -101,7 +101,7 @@ func init() {
 				} else if encryptedAttr != nil && encryptedAttr.Type() == cty.Bool && encryptedAttr.Value().False() {
 					results = append(results,
 						check.NewResultWithValueAnnotation(
-							fmt.Sprintf("Resource '%s' uses an unencrypted EBS block device.", block.Name()),
+							fmt.Sprintf("Resource '%s' uses an unencrypted EBS block device.", block.FullName()),
 							encryptedAttr.Range(),
 							encryptedAttr,
 							scanner.SeverityError,
diff --git a/internal/app/tfsec/checks/aws015.go b/internal/app/tfsec/checks/aws015.go
index 575ff7082..c449bd086 100644
--- a/internal/app/tfsec/checks/aws015.go
+++ b/internal/app/tfsec/checks/aws015.go
@@ -45,7 +45,7 @@ func init() {
 			if kmsKeyIDAttr == nil {
 				return []scanner.Result{
 					check.NewResult(
-						fmt.Sprintf("Resource '%s' defines an unencrypted SQS queue.", block.Name()),
+						fmt.Sprintf("Resource '%s' defines an unencrypted SQS queue.", block.FullName()),
 						block.Range(),
 						scanner.SeverityError,
 					),
@@ -53,7 +53,7 @@ func init() {
 			} else if kmsKeyIDAttr.Type() == cty.String && kmsKeyIDAttr.Value().AsString() == "" {
 				return []scanner.Result{
 					check.NewResultWithValueAnnotation(
-						fmt.Sprintf("Resource '%s' defines an unencrypted SQS queue.", block.Name()),
+						fmt.Sprintf("Resource '%s' defines an unencrypted SQS queue.", block.FullName()),
 						kmsKeyIDAttr.Range(),
 						kmsKeyIDAttr,
 						scanner.SeverityError,
diff --git a/internal/app/tfsec/checks/aws016.go b/internal/app/tfsec/checks/aws016.go
index 0ba48ea20..2805e382d 100644
--- a/internal/app/tfsec/checks/aws016.go
+++ b/internal/app/tfsec/checks/aws016.go
@@ -45,7 +45,7 @@ func init() {
 			if kmsKeyIDAttr == nil {
 				return []scanner.Result{
 					check.NewResult(
-						fmt.Sprintf("Resource '%s' defines an unencrypted SNS topic.", block.Name()),
+						fmt.Sprintf("Resource '%s' defines an unencrypted SNS topic.", block.FullName()),
 						block.Range(),
 						scanner.SeverityError,
 					),
@@ -53,7 +53,7 @@ func init() {
 			} else if kmsKeyIDAttr.Type() == cty.String && kmsKeyIDAttr.Value().AsString() == "" {
 				return []scanner.Result{
 					check.NewResultWithValueAnnotation(
-						fmt.Sprintf("Resource '%s' defines an unencrypted SNS topic.", block.Name()),
+						fmt.Sprintf("Resource '%s' defines an unencrypted SNS topic.", block.FullName()),
 						kmsKeyIDAttr.Range(),
 						kmsKeyIDAttr,
 						scanner.SeverityError,
diff --git a/internal/app/tfsec/checks/aws017.go b/internal/app/tfsec/checks/aws017.go
index 54830f168..af3608c20 100644
--- a/internal/app/tfsec/checks/aws017.go
+++ b/internal/app/tfsec/checks/aws017.go
@@ -52,7 +52,7 @@ func init() {
 			if encryptionBlock == nil {
 				return []scanner.Result{
 					check.NewResult(
-						fmt.Sprintf("Resource '%s' defines an unencrypted S3 bucket (missing server_side_encryption_configuration block).", block.Name()),
+						fmt.Sprintf("Resource '%s' defines an unencrypted S3 bucket (missing server_side_encryption_configuration block).", block.FullName()),
 						block.Range(),
 						scanner.SeverityError,
 					),
@@ -63,7 +63,7 @@ func init() {
 			if ruleBlock == nil {
 				return []scanner.Result{
 					check.NewResult(
-						fmt.Sprintf("Resource '%s' defines an unencrypted S3 bucket (missing rule block).", block.Name()),
+						fmt.Sprintf("Resource '%s' defines an unencrypted S3 bucket (missing rule block).", block.FullName()),
 						encryptionBlock.Range(),
 						scanner.SeverityError,
 					),
@@ -74,7 +74,7 @@ func init() {
 			if applyBlock == nil {
 				return []scanner.Result{
 					check.NewResult(
-						fmt.Sprintf("Resource '%s' defines an unencrypted S3 bucket (missing apply_server_side_encryption_by_default block).", block.Name()),
+						fmt.Sprintf("Resource '%s' defines an unencrypted S3 bucket (missing apply_server_side_encryption_by_default block).", block.FullName()),
 						ruleBlock.Range(),
 						scanner.SeverityError,
 					),
@@ -84,7 +84,7 @@ func init() {
 			if sseAttr := applyBlock.GetAttribute("sse_algorithm"); sseAttr == nil {
 				return []scanner.Result{
 					check.NewResult(
-						fmt.Sprintf("Resource '%s' defines an unencrypted S3 bucket (missing sse_algorithm attribute).", block.Name()),
+						fmt.Sprintf("Resource '%s' defines an unencrypted S3 bucket (missing sse_algorithm attribute).", block.FullName()),
 						applyBlock.Range(),
 						scanner.SeverityError,
 					),
diff --git a/internal/app/tfsec/checks/aws018.go b/internal/app/tfsec/checks/aws018.go
index e3c0603b6..4197ee1ae 100644
--- a/internal/app/tfsec/checks/aws018.go
+++ b/internal/app/tfsec/checks/aws018.go
@@ -69,7 +69,7 @@ func init() {
 			if descriptionAttr == nil {
 				return []scanner.Result{
 					check.NewResult(
-						fmt.Sprintf("Resource '%s' should include a description for auditing purposes.", block.Name()),
+						fmt.Sprintf("Resource '%s' should include a description for auditing purposes.", block.FullName()),
 						block.Range(),
 						scanner.SeverityError,
 					),
@@ -79,7 +79,7 @@ func init() {
 			if descriptionAttr.Type() == cty.String && descriptionAttr.Value().AsString() == "" {
 				return []scanner.Result{
 					check.NewResultWithValueAnnotation(
-						fmt.Sprintf("Resource '%s' should include a non-empty description for auditing purposes.", block.Name()),
+						fmt.Sprintf("Resource '%s' should include a non-empty description for auditing purposes.", block.FullName()),
 						descriptionAttr.Range(),
 						descriptionAttr,
 						scanner.SeverityError,
diff --git a/internal/app/tfsec/checks/aws019.go b/internal/app/tfsec/checks/aws019.go
index e9792893a..193e47fc4 100644
--- a/internal/app/tfsec/checks/aws019.go
+++ b/internal/app/tfsec/checks/aws019.go
@@ -46,7 +46,7 @@ func init() {
 			if keyRotationAttr == nil {
 				return []scanner.Result{
 					check.NewResult(
-						fmt.Sprintf("Resource '%s' does not have KMS Key auto-rotation enabled.", block.Name()),
+						fmt.Sprintf("Resource '%s' does not have KMS Key auto-rotation enabled.", block.FullName()),
 						block.Range(),
 						scanner.SeverityWarning,
 					),
@@ -56,7 +56,7 @@ func init() {
 			if keyRotationAttr.Type() == cty.Bool && keyRotationAttr.Value().False() {
 				return []scanner.Result{
 					check.NewResultWithValueAnnotation(
-						fmt.Sprintf("Resource '%s' does not have KMS Key auto-rotation enabled.", block.Name()),
+						fmt.Sprintf("Resource '%s' does not have KMS Key auto-rotation enabled.", block.FullName()),
 						keyRotationAttr.Range(),
 						keyRotationAttr,
 						scanner.SeverityWarning,
diff --git a/internal/app/tfsec/checks/aws020.go b/internal/app/tfsec/checks/aws020.go
index 33debaddd..3d54b54ba 100644
--- a/internal/app/tfsec/checks/aws020.go
+++ b/internal/app/tfsec/checks/aws020.go
@@ -53,7 +53,7 @@ func init() {
 			if defaultBehaviorBlock == nil {
 				results = append(results,
 					check.NewResult(
-						fmt.Sprintf("Resource '%s' defines a CloudFront distribution that allows unencrypted communications (missing default_cache_behavior block).", block.Name()),
+						fmt.Sprintf("Resource '%s' defines a CloudFront distribution that allows unencrypted communications (missing default_cache_behavior block).", block.FullName()),
 						block.Range(),
 						scanner.SeverityError,
 					),
@@ -63,7 +63,7 @@ func init() {
 				if protocolPolicy == nil {
 					results = append(results,
 						check.NewResult(
-							fmt.Sprintf("Resource '%s' defines a CloudFront distribution that allows unencrypted communications (missing viewer_protocol_policy block).", block.Name()),
+							fmt.Sprintf("Resource '%s' defines a CloudFront distribution that allows unencrypted communications (missing viewer_protocol_policy block).", block.FullName()),
 							block.Range(),
 							scanner.SeverityError,
 						),
@@ -71,7 +71,7 @@ func init() {
 				} else if protocolPolicy.Type() == cty.String && protocolPolicy.Value().AsString() == "allow-all" {
 					results = append(results,
 						check.NewResultWithValueAnnotation(
-							fmt.Sprintf("Resource '%s' defines a CloudFront distribution that allows unencrypted communications.", block.Name()),
+							fmt.Sprintf("Resource '%s' defines a CloudFront distribution that allows unencrypted communications.", block.FullName()),
 							protocolPolicy.Range(),
 							protocolPolicy,
 							scanner.SeverityError,
@@ -86,7 +86,7 @@ func init() {
 				if orderedProtocolPolicy == nil {
 					results = append(results,
 						check.NewResult(
-							fmt.Sprintf("Resource '%s' defines a CloudFront distribution that allows unencrypted communications (missing viewer_protocol_policy block).", block.Name()),
+							fmt.Sprintf("Resource '%s' defines a CloudFront distribution that allows unencrypted communications (missing viewer_protocol_policy block).", block.FullName()),
 							block.Range(),
 							scanner.SeverityError,
 						),
@@ -94,7 +94,7 @@ func init() {
 				} else if orderedProtocolPolicy != nil && orderedProtocolPolicy.Type() == cty.String && orderedProtocolPolicy.Value().AsString() == "allow-all" {
 					results = append(results,
 						check.NewResultWithValueAnnotation(
-							fmt.Sprintf("Resource '%s' defines a CloudFront distribution that allows unencrypted communications.", block.Name()),
+							fmt.Sprintf("Resource '%s' defines a CloudFront distribution that allows unencrypted communications.", block.FullName()),
 							orderedProtocolPolicy.Range(),
 							orderedProtocolPolicy,
 							scanner.SeverityError,
diff --git a/internal/app/tfsec/checks/aws021.go b/internal/app/tfsec/checks/aws021.go
index f52531def..c9a51ef4f 100644
--- a/internal/app/tfsec/checks/aws021.go
+++ b/internal/app/tfsec/checks/aws021.go
@@ -50,7 +50,7 @@ func init() {
 			if viewerCertificateBlock == nil {
 				return []scanner.Result{
 					check.NewResult(
-						fmt.Sprintf("Resource '%s' defines outdated SSL/TLS policies (missing viewer_certificate block)", block.Name()),
+						fmt.Sprintf("Resource '%s' defines outdated SSL/TLS policies (missing viewer_certificate block)", block.FullName()),
 						viewerCertificateBlock.Range(),
 						scanner.SeverityError,
 					),
@@ -60,7 +60,7 @@ func init() {
 			if minVersion := viewerCertificateBlock.GetAttribute("minimum_protocol_version"); minVersion == nil {
 				return []scanner.Result{
 					check.NewResult(
-						fmt.Sprintf("Resource '%s' defines outdated SSL/TLS policies (missing minimum_protocol_version attribute)", block.Name()),
+						fmt.Sprintf("Resource '%s' defines outdated SSL/TLS policies (missing minimum_protocol_version attribute)", block.FullName()),
 						viewerCertificateBlock.Range(),
 						scanner.SeverityError,
 					),
@@ -68,7 +68,7 @@ func init() {
 			} else if minVersion.Type() == cty.String && minVersion.Value().AsString() != "TLSv1.2_2019" {
 				return []scanner.Result{
 					check.NewResult(
-						fmt.Sprintf("Resource '%s' defines outdated SSL/TLS policies (not using TLSv1.2_2019)", block.Name()),
+						fmt.Sprintf("Resource '%s' defines outdated SSL/TLS policies (not using TLSv1.2_2019)", block.FullName()),
 						minVersion.Range(),
 						scanner.SeverityError,
 					),
diff --git a/internal/app/tfsec/checks/aws022.go b/internal/app/tfsec/checks/aws022.go
index 10fd8da9b..b6202b710 100644
--- a/internal/app/tfsec/checks/aws022.go
+++ b/internal/app/tfsec/checks/aws022.go
@@ -56,7 +56,7 @@ func init() {
 			if defaultBehaviorBlock == nil {
 				results = append(results,
 					check.NewResult(
-						fmt.Sprintf("Resource '%s' defines a MSK cluster that allows plaintext as well as TLS encrypted data in transit (missing encryption_info block).", block.Name()),
+						fmt.Sprintf("Resource '%s' defines a MSK cluster that allows plaintext as well as TLS encrypted data in transit (missing encryption_info block).", block.FullName()),
 						block.Range(),
 						scanner.SeverityWarning,
 					),
@@ -66,7 +66,7 @@ func init() {
 				if encryptionInTransit == nil {
 					results = append(results,
 						check.NewResult(
-							fmt.Sprintf("Resource '%s' defines a MSK cluster that allows plaintext as well as TLS encrypted data in transit (missing encryption_in_transit block).", block.Name()),
+							fmt.Sprintf("Resource '%s' defines a MSK cluster that allows plaintext as well as TLS encrypted data in transit (missing encryption_in_transit block).", block.FullName()),
 							block.Range(),
 							scanner.SeverityWarning,
 						),
@@ -76,7 +76,7 @@ func init() {
 					if clientBroker == nil {
 						results = append(results,
 							check.NewResult(
-								fmt.Sprintf("Resource '%s' defines a MSK cluster that allows plaintext as well as TLS encrypted data in transit (missing client_broker block).", block.Name()),
+								fmt.Sprintf("Resource '%s' defines a MSK cluster that allows plaintext as well as TLS encrypted data in transit (missing client_broker block).", block.FullName()),
 								block.Range(),
 								scanner.SeverityWarning,
 							),
@@ -84,7 +84,7 @@ func init() {
 					} else if clientBroker != nil && clientBroker.Value().AsString() == "PLAINTEXT" {
 						results = append(results,
 							check.NewResultWithValueAnnotation(
-								fmt.Sprintf("Resource '%s' defines a MSK cluster that only allows plaintext data in transit.", block.Name()),
+								fmt.Sprintf("Resource '%s' defines a MSK cluster that only allows plaintext data in transit.", block.FullName()),
 								clientBroker.Range(),
 								clientBroker,
 								scanner.SeverityError,
@@ -93,7 +93,7 @@ func init() {
 					} else if clientBroker != nil && clientBroker.Value().AsString() == "TLS_PLAINTEXT" {
 						results = append(results,
 							check.NewResultWithValueAnnotation(
-								fmt.Sprintf("Resource '%s' defines a MSK cluster that allows plaintext as well as TLS encrypted data in transit.", block.Name()),
+								fmt.Sprintf("Resource '%s' defines a MSK cluster that allows plaintext as well as TLS encrypted data in transit.", block.FullName()),
 								clientBroker.Range(),
 								clientBroker,
 								scanner.SeverityWarning,
diff --git a/internal/app/tfsec/checks/aws023.go b/internal/app/tfsec/checks/aws023.go
index 66197c268..29a3f1fab 100644
--- a/internal/app/tfsec/checks/aws023.go
+++ b/internal/app/tfsec/checks/aws023.go
@@ -58,7 +58,7 @@ func init() {
 			if ecrScanStatusAttr == nil {
 				return []scanner.Result{
 					check.NewResult(
-						fmt.Sprintf("Resource '%s' defines a disabled ECR image scan.", block.Name()),
+						fmt.Sprintf("Resource '%s' defines a disabled ECR image scan.", block.FullName()),
 						block.Range(),
 						scanner.SeverityError,
 					),
@@ -66,7 +66,7 @@ func init() {
 			} else if ecrScanStatusAttr.Type() == cty.Bool && ecrScanStatusAttr.Value().False() {
 				return []scanner.Result{
 					check.NewResultWithValueAnnotation(
-						fmt.Sprintf("Resource '%s' defines a disabled ECR image scan.", block.Name()),
+						fmt.Sprintf("Resource '%s' defines a disabled ECR image scan.", block.FullName()),
 						ecrScanStatusAttr.Range(),
 						ecrScanStatusAttr,
 						scanner.SeverityError,
diff --git a/internal/app/tfsec/checks/aws024.go b/internal/app/tfsec/checks/aws024.go
index 5a7eaf00c..6eb86db18 100644
--- a/internal/app/tfsec/checks/aws024.go
+++ b/internal/app/tfsec/checks/aws024.go
@@ -47,7 +47,7 @@ func init() {
 			if encryptionTypeAttr == nil {
 				return []scanner.Result{
 					check.NewResult(
-						fmt.Sprintf("Resource '%s' defines an unencrypted Kinesis Stream.", block.Name()),
+						fmt.Sprintf("Resource '%s' defines an unencrypted Kinesis Stream.", block.FullName()),
 						block.Range(),
 						scanner.SeverityError,
 					),
@@ -55,7 +55,7 @@ func init() {
 			} else if encryptionTypeAttr.Type() == cty.String && strings.ToUpper(encryptionTypeAttr.Value().AsString()) != "KMS" {
 				return []scanner.Result{
 					check.NewResultWithValueAnnotation(
-						fmt.Sprintf("Resource '%s' defines an unencrypted Kinesis Stream.", block.Name()),
+						fmt.Sprintf("Resource '%s' defines an unencrypted Kinesis Stream.", block.FullName()),
 						encryptionTypeAttr.Range(),
 						encryptionTypeAttr,
 						scanner.SeverityError,
@@ -66,7 +66,7 @@ func init() {
 				if keyIDAttr == nil || keyIDAttr.Value().AsString() == "" || keyIDAttr.Value().AsString() == "alias/aws/kinesis" {
 					return []scanner.Result{
 						check.NewResult(
-							fmt.Sprintf("Resource '%s' defines a Kinesis Stream encrypted with the default Kinesis key.", block.Name()),
+							fmt.Sprintf("Resource '%s' defines a Kinesis Stream encrypted with the default Kinesis key.", block.FullName()),
 							block.Range(),
 							scanner.SeverityWarning,
 						),
diff --git a/internal/app/tfsec/checks/aws025.go b/internal/app/tfsec/checks/aws025.go
index 916249d52..a97da2109 100644
--- a/internal/app/tfsec/checks/aws025.go
+++ b/internal/app/tfsec/checks/aws025.go
@@ -44,7 +44,7 @@ func init() {
 			if securityPolicyAttr == nil {
 				return []scanner.Result{
 					check.NewResult(
-						fmt.Sprintf("Resource '%s' should include security_policy (defauls to outdated SSL/TLS policy).", block.Name()),
+						fmt.Sprintf("Resource '%s' should include security_policy (defauls to outdated SSL/TLS policy).", block.FullName()),
 						block.Range(),
 						scanner.SeverityError,
 					),
@@ -54,7 +54,7 @@ func init() {
 			if securityPolicyAttr.Type() == cty.String && securityPolicyAttr.Value().AsString() != "TLS_1_2" {
 				return []scanner.Result{
 					check.NewResultWithValueAnnotation(
-						fmt.Sprintf("Resource '%s' defines outdated SSL/TLS policies (not using TLS_1_2).", block.Name()),
+						fmt.Sprintf("Resource '%s' defines outdated SSL/TLS policies (not using TLS_1_2).", block.FullName()),
 						securityPolicyAttr.Range(),
 						securityPolicyAttr,
 						scanner.SeverityError,
diff --git a/internal/app/tfsec/checks/aws031.go b/internal/app/tfsec/checks/aws031.go
index 18bd53ab1..713a42261 100644
--- a/internal/app/tfsec/checks/aws031.go
+++ b/internal/app/tfsec/checks/aws031.go
@@ -53,7 +53,7 @@ func init() {
 			if encryptionBlock == nil {
 				return []scanner.Result{
 					check.NewResult(
-						fmt.Sprintf("Resource '%s' defines an unencrypted Elasticsearch domain (missing encrypt_at_rest block).", block.Name()),
+						fmt.Sprintf("Resource '%s' defines an unencrypted Elasticsearch domain (missing encrypt_at_rest block).", block.FullName()),
 						block.Range(),
 						scanner.SeverityError,
 					),
@@ -64,7 +64,7 @@ func init() {
 			if enabledAttr == nil {
 				return []scanner.Result{
 					check.NewResult(
-						fmt.Sprintf("Resource '%s' defines an unencrypted Elasticsearch domain (missing enabled attribute).", block.Name()),
+						fmt.Sprintf("Resource '%s' defines an unencrypted Elasticsearch domain (missing enabled attribute).", block.FullName()),
 						encryptionBlock.Range(),
 						scanner.SeverityError,
 					),
@@ -78,7 +78,7 @@ func init() {
 			if !encryptionEnabled {
 				return []scanner.Result{
 					check.NewResult(
-						fmt.Sprintf("Resource '%s' defines an unencrypted Elasticsearch domain (enabled attribute set to false).", block.Name()),
+						fmt.Sprintf("Resource '%s' defines an unencrypted Elasticsearch domain (enabled attribute set to false).", block.FullName()),
 						encryptionBlock.Range(),
 						scanner.SeverityError,
 					),
diff --git a/internal/app/tfsec/checks/aws032.go b/internal/app/tfsec/checks/aws032.go
index cc867c406..ec2e89e3c 100644
--- a/internal/app/tfsec/checks/aws032.go
+++ b/internal/app/tfsec/checks/aws032.go
@@ -54,7 +54,7 @@ func init() {
 			if encryptionBlock == nil {
 				return []scanner.Result{
 					check.NewResult(
-						fmt.Sprintf("Resource '%s' defines an Elasticsearch domain with plaintext traffic (missing node_to_node_encryption block).", block.Name()),
+						fmt.Sprintf("Resource '%s' defines an Elasticsearch domain with plaintext traffic (missing node_to_node_encryption block).", block.FullName()),
 						block.Range(),
 						scanner.SeverityError,
 					),
@@ -65,7 +65,7 @@ func init() {
 			if enabledAttr == nil {
 				return []scanner.Result{
 					check.NewResult(
-						fmt.Sprintf("Resource '%s' defines an Elasticsearch domain with plaintext traffic (missing enabled attribute).", block.Name()),
+						fmt.Sprintf("Resource '%s' defines an Elasticsearch domain with plaintext traffic (missing enabled attribute).", block.FullName()),
 						encryptionBlock.Range(),
 						scanner.SeverityError,
 					),
@@ -79,7 +79,7 @@ func init() {
 			if !nodeToNodeEncryptionEnabled {
 				return []scanner.Result{
 					check.NewResult(
-						fmt.Sprintf("Resource '%s' defines an Elasticsearch domain with plaintext traffic (enabled attribute set to false).", block.Name()),
+						fmt.Sprintf("Resource '%s' defines an Elasticsearch domain with plaintext traffic (enabled attribute set to false).", block.FullName()),
 						encryptionBlock.Range(),
 						scanner.SeverityError,
 					),
diff --git a/internal/app/tfsec/checks/aws033.go b/internal/app/tfsec/checks/aws033.go
index 87f80ccc2..1e034d54a 100644
--- a/internal/app/tfsec/checks/aws033.go
+++ b/internal/app/tfsec/checks/aws033.go
@@ -57,7 +57,7 @@ func init() {
 			if endpointBlock == nil {
 				return []scanner.Result{
 					check.NewResult(
-						fmt.Sprintf("Resource '%s' defines an Elasticsearch domain with plaintext traffic (missing domain_endpoint_options block).", block.Name()),
+						fmt.Sprintf("Resource '%s' defines an Elasticsearch domain with plaintext traffic (missing domain_endpoint_options block).", block.FullName()),
 						block.Range(),
 						scanner.SeverityError,
 					),
@@ -68,7 +68,7 @@ func init() {
 			if enforceHTTPSAttr == nil {
 				return []scanner.Result{
 					check.NewResult(
-						fmt.Sprintf("Resource '%s' defines an Elasticsearch domain with plaintext traffic (missing enforce_https attribute).", block.Name()),
+						fmt.Sprintf("Resource '%s' defines an Elasticsearch domain with plaintext traffic (missing enforce_https attribute).", block.FullName()),
 						endpointBlock.Range(),
 						scanner.SeverityError,
 					),
@@ -82,7 +82,7 @@ func init() {
 			if !enforcedHTTPS {
 				return []scanner.Result{
 					check.NewResult(
-						fmt.Sprintf("Resource '%s' defines an Elasticsearch domain with plaintext traffic (enabled attribute set to false).", block.Name()),
+						fmt.Sprintf("Resource '%s' defines an Elasticsearch domain with plaintext traffic (enabled attribute set to false).", block.FullName()),
 						endpointBlock.Range(),
 						scanner.SeverityError,
 					),
diff --git a/internal/app/tfsec/checks/aws034.go b/internal/app/tfsec/checks/aws034.go
index 0e9a33cf0..50e719f44 100644
--- a/internal/app/tfsec/checks/aws034.go
+++ b/internal/app/tfsec/checks/aws034.go
@@ -61,7 +61,7 @@ func init() {
 			if tlsPolicyAttr == nil {
 				return []scanner.Result{
 					check.NewResult(
-						fmt.Sprintf("Resource '%s' defines an Elasticsearch domain with an outdated TLS policy (defaults to Policy-Min-TLS-1-0-2019-07).", block.Name()),
+						fmt.Sprintf("Resource '%s' defines an Elasticsearch domain with an outdated TLS policy (defaults to Policy-Min-TLS-1-0-2019-07).", block.FullName()),
 						endpointBlock.Range(),
 						scanner.SeverityError,
 					),
@@ -73,7 +73,7 @@ func init() {
 			if tlsPolicyAttr.Value().Equals(cty.StringVal("Policy-Min-TLS-1-0-2019-07")).True() {
 				return []scanner.Result{
 					check.NewResultWithValueAnnotation(
-						fmt.Sprintf("Resource '%s' defines an Elasticsearch domain with an outdated TLS policy (set to Policy-Min-TLS-1-0-2019-07).", block.Name()),
+						fmt.Sprintf("Resource '%s' defines an Elasticsearch domain with an outdated TLS policy (set to Policy-Min-TLS-1-0-2019-07).", block.FullName()),
 						tlsPolicyAttr.Range(),
 						tlsPolicyAttr,
 						scanner.SeverityError,
diff --git a/internal/app/tfsec/checks/aws035.go b/internal/app/tfsec/checks/aws035.go
index abff36b20..b23819f3b 100644
--- a/internal/app/tfsec/checks/aws035.go
+++ b/internal/app/tfsec/checks/aws035.go
@@ -49,7 +49,7 @@ func init() {
 			if encryptionAttr == nil {
 				return []scanner.Result{
 					check.NewResult(
-						fmt.Sprintf("Resource '%s' defines an unencrypted Elasticache Replication Group (missing at_rest_encryption_enabled attribute).", block.Name()),
+						fmt.Sprintf("Resource '%s' defines an unencrypted Elasticache Replication Group (missing at_rest_encryption_enabled attribute).", block.FullName()),
 						block.Range(),
 						scanner.SeverityError,
 					),
@@ -57,7 +57,7 @@ func init() {
 			} else if !isBooleanOrStringTrue(encryptionAttr) {
 				return []scanner.Result{
 					check.NewResultWithValueAnnotation(
-						fmt.Sprintf("Resource '%s' defines an unencrypted Elasticache Replication Group (at_rest_encryption_enabled set to false).", block.Name()),
+						fmt.Sprintf("Resource '%s' defines an unencrypted Elasticache Replication Group (at_rest_encryption_enabled set to false).", block.FullName()),
 						encryptionAttr.Range(),
 						encryptionAttr,
 						scanner.SeverityError,
diff --git a/internal/app/tfsec/checks/aws036.go b/internal/app/tfsec/checks/aws036.go
index a4b1160e4..37999c87a 100644
--- a/internal/app/tfsec/checks/aws036.go
+++ b/internal/app/tfsec/checks/aws036.go
@@ -49,7 +49,7 @@ func init() {
 			if encryptionAttr == nil {
 				return []scanner.Result{
 					check.NewResult(
-						fmt.Sprintf("Resource '%s' defines an unencrypted Elasticache Replication Group (missing transit_encryption_enabled attribute).", block.Name()),
+						fmt.Sprintf("Resource '%s' defines an unencrypted Elasticache Replication Group (missing transit_encryption_enabled attribute).", block.FullName()),
 						block.Range(),
 						scanner.SeverityError,
 					),
@@ -57,7 +57,7 @@ func init() {
 			} else if !isBooleanOrStringTrue(encryptionAttr) {
 				return []scanner.Result{
 					check.NewResultWithValueAnnotation(
-						fmt.Sprintf("Resource '%s' defines an unencrypted Elasticache Replication Group (transit_encryption_enabled set to false).", block.Name()),
+						fmt.Sprintf("Resource '%s' defines an unencrypted Elasticache Replication Group (transit_encryption_enabled set to false).", block.FullName()),
 						encryptionAttr.Range(),
 						encryptionAttr,
 						scanner.SeverityError,
diff --git a/internal/app/tfsec/checks/aws037.go b/internal/app/tfsec/checks/aws037.go
index 4d4ec5a7a..94aadec90 100644
--- a/internal/app/tfsec/checks/aws037.go
+++ b/internal/app/tfsec/checks/aws037.go
@@ -55,7 +55,7 @@ func init() {
 			if attr := block.GetAttribute("password_reuse_prevention"); attr == nil {
 				return []scanner.Result{
 					check.NewResult(
-						fmt.Sprintf("Resource '%s' does not have a password reuse prevention count set.", block.Name()),
+						fmt.Sprintf("Resource '%s' does not have a password reuse prevention count set.", block.FullName()),
 						block.Range(),
 						scanner.SeverityWarning,
 					),
@@ -65,7 +65,7 @@ func init() {
 				if value < 5 {
 					return []scanner.Result{
 						check.NewResult(
-							fmt.Sprintf("Resource '%s' has a password reuse count less than 5.", block.Name()),
+							fmt.Sprintf("Resource '%s' has a password reuse count less than 5.", block.FullName()),
 							block.Range(),
 							scanner.SeverityWarning,
 						),
diff --git a/internal/app/tfsec/checks/aws038.go b/internal/app/tfsec/checks/aws038.go
index 1d016f26a..fd482606b 100644
--- a/internal/app/tfsec/checks/aws038.go
+++ b/internal/app/tfsec/checks/aws038.go
@@ -54,7 +54,7 @@ func init() {
 			if attr := block.GetAttribute("max_password_age"); attr == nil {
 				return []scanner.Result{
 					check.NewResult(
-						fmt.Sprintf("Resource '%s' does not have a max password age set.", block.Name()),
+						fmt.Sprintf("Resource '%s' does not have a max password age set.", block.FullName()),
 						block.Range(),
 						scanner.SeverityWarning,
 					),
@@ -64,7 +64,7 @@ func init() {
 				if value > 90 {
 					return []scanner.Result{
 						check.NewResult(
-							fmt.Sprintf("Resource '%s' has a max age set which is greated than 90 days.", block.Name()),
+							fmt.Sprintf("Resource '%s' has a max age set which is greated than 90 days.", block.FullName()),
 							block.Range(),
 							scanner.SeverityWarning,
 						),
diff --git a/internal/app/tfsec/checks/aws039.go b/internal/app/tfsec/checks/aws039.go
index 2b18c4c3f..36acc0ca1 100644
--- a/internal/app/tfsec/checks/aws039.go
+++ b/internal/app/tfsec/checks/aws039.go
@@ -54,7 +54,7 @@ func init() {
 			if attr := block.GetAttribute("minimum_password_length"); attr == nil {
 				return []scanner.Result{
 					check.NewResult(
-						fmt.Sprintf("Resource '%s' does not have a minimum password length set.", block.Name()),
+						fmt.Sprintf("Resource '%s' does not have a minimum password length set.", block.FullName()),
 						block.Range(),
 						scanner.SeverityWarning,
 					),
@@ -64,7 +64,7 @@ func init() {
 				if value < 14 {
 					return []scanner.Result{
 						check.NewResult(
-							fmt.Sprintf("Resource '%s' has a minimum password length which is less than 14 characters.", block.Name()),
+							fmt.Sprintf("Resource '%s' has a minimum password length which is less than 14 characters.", block.FullName()),
 							block.Range(),
 							scanner.SeverityWarning,
 						),
diff --git a/internal/app/tfsec/checks/aws040.go b/internal/app/tfsec/checks/aws040.go
index ba85b24f5..8897026ce 100644
--- a/internal/app/tfsec/checks/aws040.go
+++ b/internal/app/tfsec/checks/aws040.go
@@ -52,7 +52,7 @@ func init() {
 			if attr := block.GetAttribute("require_symbols"); attr == nil {
 				return []scanner.Result{
 					check.NewResult(
-						fmt.Sprintf("Resource '%s' does not require a symbol in the password.", block.Name()),
+						fmt.Sprintf("Resource '%s' does not require a symbol in the password.", block.FullName()),
 						block.Range(),
 						scanner.SeverityWarning,
 					),
@@ -61,7 +61,7 @@ func init() {
 				if attr.Value().False() {
 					return []scanner.Result{
 						check.NewResult(
-							fmt.Sprintf("Resource '%s' explicitly specifies not requiring at least one symbol in the password.", block.Name()),
+							fmt.Sprintf("Resource '%s' explicitly specifies not requiring at least one symbol in the password.", block.FullName()),
 							block.Range(),
 							scanner.SeverityWarning,
 						),
diff --git a/internal/app/tfsec/checks/aws041.go b/internal/app/tfsec/checks/aws041.go
index 881f09e55..2ec9859e4 100644
--- a/internal/app/tfsec/checks/aws041.go
+++ b/internal/app/tfsec/checks/aws041.go
@@ -52,7 +52,7 @@ func init() {
 			if attr := block.GetAttribute("require_numbers"); attr == nil {
 				return []scanner.Result{
 					check.NewResult(
-						fmt.Sprintf("Resource '%s' does not require a number in the password.", block.Name()),
+						fmt.Sprintf("Resource '%s' does not require a number in the password.", block.FullName()),
 						block.Range(),
 						scanner.SeverityWarning,
 					),
@@ -61,7 +61,7 @@ func init() {
 				if attr.Value().False() {
 					return []scanner.Result{
 						check.NewResult(
-							fmt.Sprintf("Resource '%s' explicitly specifies not requiring at least one number in the password.", block.Name()),
+							fmt.Sprintf("Resource '%s' explicitly specifies not requiring at least one number in the password.", block.FullName()),
 							block.Range(),
 							scanner.SeverityWarning,
 						),
diff --git a/internal/app/tfsec/checks/aws042.go b/internal/app/tfsec/checks/aws042.go
index be115338d..1766d140f 100644
--- a/internal/app/tfsec/checks/aws042.go
+++ b/internal/app/tfsec/checks/aws042.go
@@ -52,7 +52,7 @@ func init() {
 			if attr := block.GetAttribute("require_lowercase_characters"); attr == nil {
 				return []scanner.Result{
 					check.NewResult(
-						fmt.Sprintf("Resource '%s' does not require a lowercase character in the password.", block.Name()),
+						fmt.Sprintf("Resource '%s' does not require a lowercase character in the password.", block.FullName()),
 						block.Range(),
 						scanner.SeverityWarning,
 					),
@@ -61,7 +61,7 @@ func init() {
 				if attr.Value().False() {
 					return []scanner.Result{
 						check.NewResult(
-							fmt.Sprintf("Resource '%s' explicitly specifies not requiring at least lowercase character in the password.", block.Name()),
+							fmt.Sprintf("Resource '%s' explicitly specifies not requiring at least lowercase character in the password.", block.FullName()),
 							block.Range(),
 							scanner.SeverityWarning,
 						),
diff --git a/internal/app/tfsec/checks/aws043.go b/internal/app/tfsec/checks/aws043.go
index 34a1238ec..343725d91 100644
--- a/internal/app/tfsec/checks/aws043.go
+++ b/internal/app/tfsec/checks/aws043.go
@@ -53,7 +53,7 @@ func init() {
 			if attr := block.GetAttribute("require_uppercase_characters"); attr == nil {
 				return []scanner.Result{
 					check.NewResult(
-						fmt.Sprintf("Resource '%s' does not require an uppercase character in the password.", block.Name()),
+						fmt.Sprintf("Resource '%s' does not require an uppercase character in the password.", block.FullName()),
 						block.Range(),
 						scanner.SeverityWarning,
 					),
@@ -62,7 +62,7 @@ func init() {
 				if attr.Value().False() {
 					return []scanner.Result{
 						check.NewResult(
-							fmt.Sprintf("Resource '%s' explicitly specifies not requiring at least one uppercase character in the password.", block.Name()),
+							fmt.Sprintf("Resource '%s' explicitly specifies not requiring at least one uppercase character in the password.", block.FullName()),
 							block.Range(),
 							scanner.SeverityWarning,
 						),
diff --git a/internal/app/tfsec/checks/aws044.go b/internal/app/tfsec/checks/aws044.go
index 1f026b6f8..3bf2e4fc2 100644
--- a/internal/app/tfsec/checks/aws044.go
+++ b/internal/app/tfsec/checks/aws044.go
@@ -45,7 +45,7 @@ func init() {
 			if accessKeyAttribute := block.GetAttribute("access_key"); accessKeyAttribute != nil && accessKeyAttribute.Type() == cty.String {
 				return []scanner.Result{
 					check.NewResultWithValueAnnotation(
-						fmt.Sprintf("Provider '%s' has an access key specified.", block.Name()),
+						fmt.Sprintf("Provider '%s' has an access key specified.", block.FullName()),
 						accessKeyAttribute.Range(),
 						accessKeyAttribute,
 						scanner.SeverityError,
@@ -54,7 +54,7 @@ func init() {
 			} else if secretKeyAttribute := block.GetAttribute("secret_key"); secretKeyAttribute != nil && secretKeyAttribute.Type() == cty.String {
 				return []scanner.Result{
 					check.NewResultWithValueAnnotation(
-						fmt.Sprintf("Provider '%s' has a secret key specified.", block.Name()),
+						fmt.Sprintf("Provider '%s' has a secret key specified.", block.FullName()),
 						secretKeyAttribute.Range(),
 						secretKeyAttribute,
 						scanner.SeverityError,
diff --git a/internal/app/tfsec/checks/aws045.go b/internal/app/tfsec/checks/aws045.go
index f9488513d..49317572e 100644
--- a/internal/app/tfsec/checks/aws045.go
+++ b/internal/app/tfsec/checks/aws045.go
@@ -98,7 +98,7 @@ func init() {
 			if wafAclIdBlock == nil {
 				return []scanner.Result{
 					check.NewResult(
-						fmt.Sprintf("Resource '%s' does not have a WAF in front of it.", block.Name()),
+						fmt.Sprintf("Resource '%s' does not have a WAF in front of it.", block.FullName()),
 						block.Range(),
 						scanner.SeverityWarning,
 					),
diff --git a/internal/app/tfsec/checks/aws046.go b/internal/app/tfsec/checks/aws046.go
index 9e3a02e09..415984f24 100644
--- a/internal/app/tfsec/checks/aws046.go
+++ b/internal/app/tfsec/checks/aws046.go
@@ -2,9 +2,10 @@ package checks
 
 import (
 	"fmt"
-	"github.com/zclconf/go-cty/cty"
 	"strings"
 
+	"github.com/zclconf/go-cty/cty"
+
 	"github.com/tfsec/tfsec/internal/app/tfsec/scanner"
 
 	"github.com/tfsec/tfsec/internal/app/tfsec/parser"
@@ -68,7 +69,7 @@ func init() {
 							if actionValue.AsString() == "*" {
 								return []scanner.Result{
 									check.NewResult(
-										fmt.Sprintf("Resource '%s' has a wildcard action specified.", block.Name()),
+										fmt.Sprintf("Resource '%s' has a wildcard action specified.", block.FullName()),
 										statementBlock.Range(),
 										scanner.SeverityError,
 									),
diff --git a/internal/app/tfsec/checks/aws047.go b/internal/app/tfsec/checks/aws047.go
index f2553e750..785cdb1d7 100644
--- a/internal/app/tfsec/checks/aws047.go
+++ b/internal/app/tfsec/checks/aws047.go
@@ -3,6 +3,7 @@ package checks
 import (
 	"encoding/json"
 	"fmt"
+	"github.com/zclconf/go-cty/cty"
 	"strings"
 
 	"github.com/tfsec/tfsec/internal/app/tfsec/parser"
@@ -59,7 +60,7 @@ func init() {
 			Explanation: AWSSqsPolicyWildcardActionsExplanation,
 			BadExample:  AWSSqsPolicyWildcardActionsBadExample,
 			GoodExample: AWSSqsPolicyWildcardActionsGoodExample,
-			Links:       []string{
+			Links: []string{
 				"https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-security-best-practices.html",
 				"https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sqs_queue_policy",
 			},
@@ -68,6 +69,11 @@ func init() {
 		RequiredTypes:  []string{"resource"},
 		RequiredLabels: []string{"aws_sqs_queue_policy"},
 		CheckFunc: func(check *scanner.Check, block *parser.Block, _ *scanner.Context) []scanner.Result {
+
+			if block.GetAttribute("policy").Value().Type() != cty.String {
+				return nil
+			}
+
 			rawJSON := []byte(block.GetAttribute("policy").Value().AsString())
 			var policy struct {
 				Statement []struct {
@@ -81,7 +87,7 @@ func init() {
 					if strings.ToLower(statement.Effect) == "allow" && (statement.Action == "*" || statement.Action == "sqs:*") {
 						return []scanner.Result{
 							check.NewResult(
-								fmt.Sprintf("SQS policy '%s' has a wildcard action specified.", block.Name()),
+								fmt.Sprintf("SQS policy '%s' has a wildcard action specified.", block.FullName()),
 								block.Range(),
 								scanner.SeverityError,
 							),
diff --git a/internal/app/tfsec/checks/azu001.go b/internal/app/tfsec/checks/azu001.go
index ee22f6c97..25b18f22d 100644
--- a/internal/app/tfsec/checks/azu001.go
+++ b/internal/app/tfsec/checks/azu001.go
@@ -62,7 +62,7 @@ func init() {
 							check.NewResultWithValueAnnotation(
 								fmt.Sprintf(
 									"Resource '%s' defines a fully open %s network security group rule.",
-									block.Name(),
+									block.FullName(),
 									strings.ToLower(directionAttr.Value().AsString()),
 								),
 								prefixAttr.Range(),
@@ -82,7 +82,7 @@ func init() {
 						if accessAttr := block.GetAttribute("access"); accessAttr != nil && accessAttr.Value().AsString() == "Allow" {
 							results = append(results,
 								check.NewResultWithValueAnnotation(
-									fmt.Sprintf("Resource '%s' defines a fully open %s security group rule.", block.Name(), prefix.AsString()),
+									fmt.Sprintf("Resource '%s' defines a fully open %s security group rule.", block.FullName(), prefix.AsString()),
 									prefixesAttr.Range(),
 									prefixesAttr,
 									scanner.SeverityWarning,
diff --git a/internal/app/tfsec/checks/azu002.go b/internal/app/tfsec/checks/azu002.go
index a3b0eb7d9..9d03da4dc 100644
--- a/internal/app/tfsec/checks/azu002.go
+++ b/internal/app/tfsec/checks/azu002.go
@@ -60,7 +60,7 @@ func init() {
 							check.NewResultWithValueAnnotation(
 								fmt.Sprintf(
 									"Resource '%s' defines a fully open %s network security group rule.",
-									block.Name(),
+									block.FullName(),
 									strings.ToLower(directionAttr.Value().AsString()),
 								),
 								prefixAttr.Range(),
@@ -80,7 +80,7 @@ func init() {
 						if accessAttr := block.GetAttribute("access"); accessAttr != nil && accessAttr.Value().AsString() == "Allow" {
 							results = append(results,
 								check.NewResultWithValueAnnotation(
-									fmt.Sprintf("Resource '%s' defines a fully open %s security group rule.", block.Name(), prefix.AsString()),
+									fmt.Sprintf("Resource '%s' defines a fully open %s security group rule.", block.FullName(), prefix.AsString()),
 									prefixesAttr.Range(),
 									prefixesAttr,
 									scanner.SeverityWarning,
diff --git a/internal/app/tfsec/checks/azu003.go b/internal/app/tfsec/checks/azu003.go
index 80cd21353..d7d10e8c6 100644
--- a/internal/app/tfsec/checks/azu003.go
+++ b/internal/app/tfsec/checks/azu003.go
@@ -53,7 +53,7 @@ func init() {
 					check.NewResult(
 						fmt.Sprintf(
 							"Resource '%s' defines an unencrypted managed disk.",
-							block.Name(),
+							block.FullName(),
 						),
 						block.Range(),
 						scanner.SeverityError,
@@ -67,7 +67,7 @@ func init() {
 					check.NewResultWithValueAnnotation(
 						fmt.Sprintf(
 							"Resource '%s' defines an unencrypted managed disk.",
-							block.Name(),
+							block.FullName(),
 						),
 						enabledAttr.Range(),
 						enabledAttr,
diff --git a/internal/app/tfsec/checks/azu004.go b/internal/app/tfsec/checks/azu004.go
index 0889d408e..96adc86e0 100644
--- a/internal/app/tfsec/checks/azu004.go
+++ b/internal/app/tfsec/checks/azu004.go
@@ -49,7 +49,7 @@ func init() {
 					check.NewResultWithValueAnnotation(
 						fmt.Sprintf(
 							"Resource '%s' defines an unencrypted data lake store.",
-							block.Name(),
+							block.FullName(),
 						),
 						encryptionStateAttr.Range(),
 						encryptionStateAttr,
diff --git a/internal/app/tfsec/checks/azu005.go b/internal/app/tfsec/checks/azu005.go
index dd0b85336..151b4a095 100644
--- a/internal/app/tfsec/checks/azu005.go
+++ b/internal/app/tfsec/checks/azu005.go
@@ -54,7 +54,7 @@ func init() {
 						check.NewResultWithValueAnnotation(
 							fmt.Sprintf(
 								"Resource '%s' has password authentication enabled. Use SSH keys instead.",
-								block.Name(),
+								block.FullName(),
 							),
 							passwordAuthDisabledAttr.Range(),
 							passwordAuthDisabledAttr,
diff --git a/internal/app/tfsec/checks/gcp001.go b/internal/app/tfsec/checks/gcp001.go
index 2134d4a7f..a6047caf3 100644
--- a/internal/app/tfsec/checks/gcp001.go
+++ b/internal/app/tfsec/checks/gcp001.go
@@ -59,7 +59,7 @@ func init() {
 				if keyBlock.GetAttribute("raw_key") == nil && keyBlock.GetAttribute("kms_key_self_link") == nil {
 					return []scanner.Result{
 						check.NewResult(
-							fmt.Sprintf("Resource '%s' defines an unencrypted disk. You should specify raw_key or kms_key_self_link.", block.Name()),
+							fmt.Sprintf("Resource '%s' defines an unencrypted disk. You should specify raw_key or kms_key_self_link.", block.FullName()),
 							keyBlock.Range(),
 							scanner.SeverityError,
 						),
diff --git a/internal/app/tfsec/checks/gcp002.go b/internal/app/tfsec/checks/gcp002.go
index fc4a47177..489086cb4 100644
--- a/internal/app/tfsec/checks/gcp002.go
+++ b/internal/app/tfsec/checks/gcp002.go
@@ -54,7 +54,7 @@ func init() {
 			if encryptionBlock == nil {
 				return []scanner.Result{
 					check.NewResult(
-						fmt.Sprintf("Resource '%s' defines an unencrypted storage bucket.", block.Name()),
+						fmt.Sprintf("Resource '%s' defines an unencrypted storage bucket.", block.FullName()),
 						block.Range(),
 						scanner.SeverityError,
 					),
diff --git a/internal/app/tfsec/checks/gcp003.go b/internal/app/tfsec/checks/gcp003.go
index 732edc917..48738a33b 100644
--- a/internal/app/tfsec/checks/gcp003.go
+++ b/internal/app/tfsec/checks/gcp003.go
@@ -54,7 +54,7 @@ func init() {
 					if strings.HasSuffix(cidr.AsString(), "/0") {
 						return []scanner.Result{
 							check.NewResult(
-								fmt.Sprintf("Resource '%s' defines a fully open inbound firewall rule.", block.Name()),
+								fmt.Sprintf("Resource '%s' defines a fully open inbound firewall rule.", block.FullName()),
 								sourceRanges.Range(),
 								scanner.SeverityWarning,
 							),
diff --git a/internal/app/tfsec/checks/gcp004.go b/internal/app/tfsec/checks/gcp004.go
index c85335ddf..5cdf241de 100644
--- a/internal/app/tfsec/checks/gcp004.go
+++ b/internal/app/tfsec/checks/gcp004.go
@@ -53,7 +53,7 @@ func init() {
 					if strings.HasSuffix(cidr.AsString(), "/0") {
 						return []scanner.Result{
 							check.NewResult(
-								fmt.Sprintf("Resource '%s' defines a fully open outbound firewall rule.", block.Name()),
+								fmt.Sprintf("Resource '%s' defines a fully open outbound firewall rule.", block.FullName()),
 								destinationRanges.Range(),
 								scanner.SeverityWarning,
 							),
diff --git a/internal/app/tfsec/checks/gcp005.go b/internal/app/tfsec/checks/gcp005.go
index a8eb21e11..731ed3973 100644
--- a/internal/app/tfsec/checks/gcp005.go
+++ b/internal/app/tfsec/checks/gcp005.go
@@ -52,7 +52,7 @@ func init() {
 			if enable_legacy_abac != nil && enable_legacy_abac.Value().Type() == cty.String && enable_legacy_abac.Value().AsString() == "true" {
 				return []scanner.Result{
 					check.NewResult(
-						fmt.Sprintf("Resource '%s' defines a cluster with ABAC enabled. Disable and rely on RBAC instead. ", block.Name()),
+						fmt.Sprintf("Resource '%s' defines a cluster with ABAC enabled. Disable and rely on RBAC instead. ", block.FullName()),
 						block.Range(),
 						scanner.SeverityError,
 					),
diff --git a/internal/app/tfsec/checks/gcp006.go b/internal/app/tfsec/checks/gcp006.go
index 5d4b2eb21..e78310119 100644
--- a/internal/app/tfsec/checks/gcp006.go
+++ b/internal/app/tfsec/checks/gcp006.go
@@ -56,7 +56,7 @@ func init() {
 				(nodeMetadata.Value().AsString() == "EXPOSE" || nodeMetadata.Value().AsString() == "UNSPECIFIED") {
 				return []scanner.Result{
 					check.NewResult(
-						fmt.Sprintf("Resource '%s' defines a cluster with node metadata exposed. node_metadata set to EXPOSE or UNSPECIFIED disables metadata concealment. ", block.Name()),
+						fmt.Sprintf("Resource '%s' defines a cluster with node metadata exposed. node_metadata set to EXPOSE or UNSPECIFIED disables metadata concealment. ", block.FullName()),
 						nodeMetadata.Range(),
 						scanner.SeverityError,
 					),
diff --git a/internal/app/tfsec/checks/gcp007.go b/internal/app/tfsec/checks/gcp007.go
index d07582d16..fdfef4a78 100644
--- a/internal/app/tfsec/checks/gcp007.go
+++ b/internal/app/tfsec/checks/gcp007.go
@@ -57,7 +57,7 @@ func init() {
 			if legacyMetadataAPI.Type() == cty.String && legacyMetadataAPI.Value().AsString() != "true" || legacyMetadataAPI.Type() == cty.Bool && legacyMetadataAPI.Value().False() {
 				return []scanner.Result{
 					check.NewResult(
-						fmt.Sprintf("Resource '%s' defines a cluster with legacy metadata endpoints enabled.", block.Name()),
+						fmt.Sprintf("Resource '%s' defines a cluster with legacy metadata endpoints enabled.", block.FullName()),
 						legacyMetadataAPI.Range(),
 						scanner.SeverityError,
 					),
diff --git a/internal/app/tfsec/checks/gcp008.go b/internal/app/tfsec/checks/gcp008.go
index 690a6f4de..91120ec94 100644
--- a/internal/app/tfsec/checks/gcp008.go
+++ b/internal/app/tfsec/checks/gcp008.go
@@ -65,7 +65,7 @@ func init() {
 			if masterAuthBlock == nil {
 				return []scanner.Result{
 					check.NewResult(
-						fmt.Sprintf("Resource '%s' does not disable basic auth with static passwords for client authentication. Disable this with a master_auth block container empty strings for user and password.", block.Name()),
+						fmt.Sprintf("Resource '%s' does not disable basic auth with static passwords for client authentication. Disable this with a master_auth block container empty strings for user and password.", block.FullName()),
 						block.Range(),
 						scanner.SeverityError,
 					),
@@ -73,7 +73,7 @@ func init() {
 			} else if staticAuthUser.Type() == cty.String && staticAuthUser.Value().AsString() != "" && staticAuthPass.Type() == cty.String && staticAuthPass.Value().AsString() != "" {
 				return []scanner.Result{
 					check.NewResult(
-						fmt.Sprintf("Resource '%s' defines a cluster using basic auth with static passwords for client authentication. It is recommended to use OAuth or service accounts instead.", block.Name()),
+						fmt.Sprintf("Resource '%s' defines a cluster using basic auth with static passwords for client authentication. It is recommended to use OAuth or service accounts instead.", block.FullName()),
 						masterAuthBlock.Range(),
 						scanner.SeverityError,
 					),
@@ -83,7 +83,7 @@ func init() {
 			if issueClientCert.Type() == cty.Bool && issueClientCert.Value().True() || issueClientCert.Type() == cty.String && issueClientCert.Value().AsString() == "true" {
 				return []scanner.Result{
 					check.NewResult(
-						fmt.Sprintf("Resource '%s' defines a cluster using basic auth with client certificates for authentication. This cert has no permissions if RBAC is enabled and ABAC is disabled. It is recommended to use OAuth or service accounts instead.", block.Name()),
+						fmt.Sprintf("Resource '%s' defines a cluster using basic auth with client certificates for authentication. This cert has no permissions if RBAC is enabled and ABAC is disabled. It is recommended to use OAuth or service accounts instead.", block.FullName()),
 						issueClientCert.Range(),
 						scanner.SeverityError,
 					),
diff --git a/internal/app/tfsec/checks/gcp009.go b/internal/app/tfsec/checks/gcp009.go
index 2feb63944..1faa97690 100644
--- a/internal/app/tfsec/checks/gcp009.go
+++ b/internal/app/tfsec/checks/gcp009.go
@@ -56,7 +56,7 @@ func init() {
 			if pspBlock == nil {
 				return []scanner.Result{
 					check.NewResult(
-						fmt.Sprintf("Resource '%s' defines a cluster with no Pod Security Policy config defined. It is recommended to define a PSP for your pods and enable PSP enforcement.", block.Name()),
+						fmt.Sprintf("Resource '%s' defines a cluster with no Pod Security Policy config defined. It is recommended to define a PSP for your pods and enable PSP enforcement.", block.FullName()),
 						block.Range(),
 						scanner.SeverityError,
 					),
@@ -67,7 +67,7 @@ func init() {
 			if enforcePSP.Type() == cty.Bool && enforcePSP.Value().False() || enforcePSP.Type() == cty.String && enforcePSP.Value().AsString() != "true" {
 				return []scanner.Result{
 					check.NewResult(
-						fmt.Sprintf("Resource '%s' defines a cluster with Pod Security Policy enforcement disabled. It is recommended to define a PSP for your pods and enable PSP enforcement.", block.Name()),
+						fmt.Sprintf("Resource '%s' defines a cluster with Pod Security Policy enforcement disabled. It is recommended to define a PSP for your pods and enable PSP enforcement.", block.FullName()),
 						enforcePSP.Range(),
 						scanner.SeverityError,
 					),
diff --git a/internal/app/tfsec/checks/gcp010.go b/internal/app/tfsec/checks/gcp010.go
index c69dedfb3..7dc90f7c9 100644
--- a/internal/app/tfsec/checks/gcp010.go
+++ b/internal/app/tfsec/checks/gcp010.go
@@ -50,7 +50,7 @@ func init() {
 			if enable_shielded_nodes == nil {
 				return []scanner.Result{
 					check.NewResult(
-						fmt.Sprintf("Resource '%s' defines a cluster with shielded nodes disabled. Shielded GKE Nodes provide strong, verifiable node identity and integrity to increase the security of GKE nodes and should be enabled on all GKE clusters.", block.Name()),
+						fmt.Sprintf("Resource '%s' defines a cluster with shielded nodes disabled. Shielded GKE Nodes provide strong, verifiable node identity and integrity to increase the security of GKE nodes and should be enabled on all GKE clusters.", block.FullName()),
 						block.Range(),
 						scanner.SeverityError,
 					),
@@ -60,7 +60,7 @@ func init() {
 			if enable_shielded_nodes.Type() == cty.Bool && enable_shielded_nodes.Value().False() || enable_shielded_nodes.Type() == cty.String && enable_shielded_nodes.Value().AsString() != "true" {
 				return []scanner.Result{
 					check.NewResult(
-						fmt.Sprintf("Resource '%s' defines a cluster with shielded nodes disabled. Shielded GKE Nodes provide strong, verifiable node identity and integrity to increase the security of GKE nodes and should be enabled on all GKE clusters.", block.Name()),
+						fmt.Sprintf("Resource '%s' defines a cluster with shielded nodes disabled. Shielded GKE Nodes provide strong, verifiable node identity and integrity to increase the security of GKE nodes and should be enabled on all GKE clusters.", block.FullName()),
 						enable_shielded_nodes.Range(),
 						scanner.SeverityError,
 					),
diff --git a/internal/app/tfsec/checks/gcp011.go b/internal/app/tfsec/checks/gcp011.go
index 4a215be0f..eec9c4473 100644
--- a/internal/app/tfsec/checks/gcp011.go
+++ b/internal/app/tfsec/checks/gcp011.go
@@ -102,7 +102,7 @@ func init() {
 				if identities.Type() == cty.String && strings.HasPrefix(identities.AsString(), "user:") {
 					return []scanner.Result{
 						check.NewResult(
-							fmt.Sprintf("'%s' grants IAM to a user object. It is recommended to manage user permissions with groups.", block.Name()),
+							fmt.Sprintf("'%s' grants IAM to a user object. It is recommended to manage user permissions with groups.", block.FullName()),
 							attributes.Range(),
 							scanner.SeverityWarning,
 						),
diff --git a/internal/app/tfsec/checks/gen001.go b/internal/app/tfsec/checks/gen001.go
index 80a012012..53a459cc0 100644
--- a/internal/app/tfsec/checks/gen001.go
+++ b/internal/app/tfsec/checks/gen001.go
@@ -76,7 +76,7 @@ func init() {
 					}
 					if val.AsString() != "" {
 						results = append(results, check.NewResultWithValueAnnotation(
-							fmt.Sprintf("Variable '%s' includes a potentially sensitive default value.", block.Name()),
+							fmt.Sprintf("Variable '%s' includes a potentially sensitive default value.", block.FullName()),
 							attribute.Range(),
 							attribute,
 							scanner.SeverityWarning,
diff --git a/internal/app/tfsec/checks/gen002.go b/internal/app/tfsec/checks/gen002.go
index c83d5347d..866b262fe 100644
--- a/internal/app/tfsec/checks/gen002.go
+++ b/internal/app/tfsec/checks/gen002.go
@@ -62,7 +62,7 @@ func init() {
 				if security.IsSensitiveAttribute(attribute.Name()) {
 					if attribute.Type() == cty.String && attribute.Value().AsString() != "" {
 						results = append(results, check.NewResultWithValueAnnotation(
-							fmt.Sprintf("Local '%s' includes a potentially sensitive value which is defined within the project.", block.Name()),
+							fmt.Sprintf("Local '%s' includes a potentially sensitive value which is defined within the project.", block.FullName()),
 							attribute.Range(),
 							attribute,
 							scanner.SeverityWarning,
diff --git a/internal/app/tfsec/checks/gen003.go b/internal/app/tfsec/checks/gen003.go
index d0cf01dda..2e44156d1 100644
--- a/internal/app/tfsec/checks/gen003.go
+++ b/internal/app/tfsec/checks/gen003.go
@@ -79,7 +79,7 @@ func init() {
 				if security.IsSensitiveAttribute(attribute.Name()) {
 					if attribute.Type() == cty.String && attribute.Value().AsString() != "" {
 						results = append(results, check.NewResultWithValueAnnotation(
-							fmt.Sprintf("Block '%s' includes a potentially sensitive attribute which is defined within the project.", block.Name()),
+							fmt.Sprintf("Block '%s' includes a potentially sensitive attribute which is defined within the project.", block.FullName()),
 							attribute.Range(),
 							attribute,
 							scanner.SeverityWarning,
diff --git a/internal/app/tfsec/custom/processing.go b/internal/app/tfsec/custom/processing.go
index fe9642e5d..54ed3f4af 100644
--- a/internal/app/tfsec/custom/processing.go
+++ b/internal/app/tfsec/custom/processing.go
@@ -2,6 +2,7 @@ package custom
 
 import (
 	"fmt"
+
 	"github.com/tfsec/tfsec/internal/app/tfsec/parser"
 	"github.com/tfsec/tfsec/internal/app/tfsec/scanner"
 )
@@ -24,7 +25,7 @@ func processFoundChecks(checks ChecksFile) {
 					if !evalMatchSpec(rootBlock, matchSpec) {
 						return []scanner.Result{
 							check.NewResult(
-								fmt.Sprintf("Custom check failed for resource %s. %s", rootBlock.Name(), customCheck.ErrorMessage),
+								fmt.Sprintf("Custom check failed for resource %s. %s", rootBlock.FullName(), customCheck.ErrorMessage),
 								rootBlock.Range(),
 								customCheck.Severity,
 							),
diff --git a/internal/app/tfsec/parser/block.go b/internal/app/tfsec/parser/block.go
index 61ad837ed..312c20de8 100644
--- a/internal/app/tfsec/parser/block.go
+++ b/internal/app/tfsec/parser/block.go
@@ -9,10 +9,9 @@ import (
 )
 
 type Block struct {
-	hclBlock *hcl.Block
-	ctx      *hcl.EvalContext
-	prefix   string
-	inModule bool
+	hclBlock    *hcl.Block
+	ctx         *hcl.EvalContext
+	moduleBlock *Block
 }
 
 type Blocks []*Block
@@ -40,10 +39,11 @@ func (blocks Blocks) RemoveDuplicates() Blocks {
 	return blockSet
 }
 
-func NewBlock(hclBlock *hcl.Block, ctx *hcl.EvalContext) *Block {
+func NewBlock(hclBlock *hcl.Block, ctx *hcl.EvalContext, moduleBlock *Block) *Block {
 	return &Block{
-		hclBlock: hclBlock,
-		ctx:      ctx,
+		ctx:         ctx,
+		hclBlock:    hclBlock,
+		moduleBlock: moduleBlock,
 	}
 }
 
@@ -77,7 +77,7 @@ func (block *Block) GetBlock(name string) *Block {
 	}
 	for _, child := range block.body().Blocks {
 		if child.Type == name {
-			return NewBlock(child.AsHCLBlock(), block.ctx)
+			return NewBlock(child.AsHCLBlock(), block.ctx, block.moduleBlock)
 		}
 		if child.Type == "dynamic" && len(child.Labels) == 1 && child.Labels[0] == name {
 			blocks := block.parseDynamicBlockResult(child)
@@ -97,7 +97,7 @@ func (block *Block) GetBlocks(name string) Blocks {
 	var results []*Block
 	for _, child := range block.body().Blocks {
 		if child.Type == name {
-			results = append(results, NewBlock(child.AsHCLBlock(), block.ctx))
+			results = append(results, NewBlock(child.AsHCLBlock(), block.ctx, block.moduleBlock))
 		}
 		if child.Type == "dynamic" && len(child.Labels) == 1 && child.Labels[0] == name {
 			dynamics := block.parseDynamicBlockResult(child)
@@ -112,7 +112,7 @@ func (block *Block) parseDynamicBlockResult(dynamic *hclsyntax.Block) Blocks {
 
 	var results Blocks
 
-	wrapped := NewBlock(dynamic.AsHCLBlock(), block.ctx)
+	wrapped := NewBlock(dynamic.AsHCLBlock(), block.ctx, block.moduleBlock)
 
 	forEach := wrapped.GetAttribute("for_each")
 	if forEach == nil {
@@ -156,25 +156,41 @@ func (block *Block) GetAttribute(name string) *Attribute {
 	return nil
 }
 
-func (block *Block) Name() string {
+// LocalName is the name relative to the current module
+func (block *Block) LocalName() string {
 	var prefix string
 	if block.Type() != "resource" {
 		prefix = block.Type() + "."
 	}
-	if block.Type() == "output" && block.inModule {
-		prefix = fmt.Sprintf("%s.", block.prefix)
-	}
 	return prefix + strings.Join(block.Labels(), ".")
 }
 
+func (block *Block) FullName() string {
+
+	if block.moduleBlock != nil {
+		return fmt.Sprintf(
+			"%s:%s",
+			block.moduleBlock.FullName(),
+			block.LocalName(),
+		)
+	}
+
+	return block.LocalName()
+}
+
 func (block *Block) HasChild(childElement string) bool {
 	return block.GetAttribute(childElement) != nil || block.GetBlock(childElement) != nil
 }
 
 func (block *Block) InModule() bool {
-	return block.inModule
+	return block.moduleBlock != nil
 }
 
 func (block *Block) identifier() string {
+	// TODO use FullName() here instead? these should be unique
 	return fmt.Sprintf("%s:%s:%s", block.Range().Filename, block.Type(), strings.Join(block.Labels(), ":"))
 }
+
+func (block *Block) Label() string {
+	return strings.Join(block.hclBlock.Labels, ".")
+}
diff --git a/internal/app/tfsec/parser/evaluator.go b/internal/app/tfsec/parser/evaluator.go
new file mode 100644
index 000000000..47c2e0c28
--- /dev/null
+++ b/internal/app/tfsec/parser/evaluator.go
@@ -0,0 +1,241 @@
+package parser
+
+import (
+	"reflect"
+
+	"github.com/hashicorp/hcl/v2"
+	"github.com/tfsec/tfsec/internal/app/tfsec/debug"
+	"github.com/zclconf/go-cty/cty"
+)
+
+const maxContextIterations = 32
+
+type Evaluator struct {
+	ctx            *hcl.EvalContext
+	blocks         Blocks
+	modules        []*ModuleInfo
+	inputVars      map[string]cty.Value
+	moduleMetadata *ModulesMetadata
+	path           string
+	moduleBasePath string
+}
+
+func NewEvaluator(path string, blocks Blocks, inputVars map[string]cty.Value, moduleMetadata *ModulesMetadata, modules []*ModuleInfo) *Evaluator {
+
+	ctx := &hcl.EvalContext{
+		Variables: make(map[string]cty.Value),
+		Functions: Functions(path),
+	}
+
+	// attach context to blocks
+	for _, block := range blocks {
+		block.ctx = ctx
+	}
+
+	return &Evaluator{
+		path:           path,
+		moduleBasePath: path,
+		ctx:            ctx,
+		blocks:         blocks,
+		inputVars:      inputVars,
+		moduleMetadata: moduleMetadata,
+		modules:        modules,
+	}
+}
+
+func (e *Evaluator) SetModuleBasePath(path string) {
+	e.moduleBasePath = path
+}
+
+func (e *Evaluator) evaluateStep(i int) {
+
+	debug.Log("Starting iteration %d of context evaluation...", i+1)
+
+	e.ctx.Variables["var"] = e.getValuesByBlockType("variable")
+	e.ctx.Variables["local"] = e.getValuesByBlockType("locals")
+	e.ctx.Variables["provider"] = e.getValuesByBlockType("provider")
+
+	resources := e.getValuesByBlockType("resource")
+	for key, resource := range resources.AsValueMap() {
+		e.ctx.Variables[key] = resource
+	}
+
+	e.ctx.Variables["data"] = e.getValuesByBlockType("data")
+	e.ctx.Variables["output"] = e.getValuesByBlockType("output")
+
+	e.evaluateModules()
+}
+
+func (e *Evaluator) evaluateModules() {
+
+	for _, module := range e.modules {
+
+		inputVars := make(map[string]cty.Value)
+		for _, attr := range module.Definition.GetAttributes() {
+			func() {
+				defer func() {
+					if err := recover(); err != nil {
+						return
+					}
+				}()
+				inputVars[attr.Name()] = attr.Value()
+			}()
+		}
+
+		childModules := LoadModules(module.Blocks, e.moduleBasePath, e.moduleMetadata)
+		moduleEvaluator := NewEvaluator(module.Path, module.Blocks, inputVars, e.moduleMetadata, childModules)
+		moduleEvaluator.SetModuleBasePath(e.moduleBasePath)
+		_, _ = moduleEvaluator.EvaluateAll()
+
+		// export module outputs
+		moduleMapRaw := e.ctx.Variables["module"]
+		if moduleMapRaw == cty.NilVal {
+			moduleMapRaw = cty.ObjectVal(make(map[string]cty.Value))
+		}
+		moduleMap := moduleMapRaw.AsValueMap()
+		if moduleMap == nil {
+			moduleMap = make(map[string]cty.Value)
+		}
+		moduleMap[module.Name] = moduleEvaluator.ExportOutputs()
+		e.ctx.Variables["module"] = cty.ObjectVal(moduleMap)
+	}
+}
+
+// export module outputs to a parent context
+func (e *Evaluator) ExportOutputs() cty.Value {
+	return e.ctx.Variables["output"]
+}
+
+func (e *Evaluator) EvaluateAll() (Blocks, error) {
+
+	var lastContext hcl.EvalContext
+
+	for i := 0; i < maxContextIterations; i++ {
+
+		e.evaluateStep(i)
+
+		// if ctx matches the last evaluation, we can bail, nothing left to resolve
+		if reflect.DeepEqual(lastContext.Variables, e.ctx.Variables) {
+			break
+		}
+
+		lastContext.Variables = make(map[string]cty.Value)
+		for k, v := range e.ctx.Variables {
+			lastContext.Variables[k] = v
+		}
+	}
+
+	var allBlocks Blocks
+	allBlocks = append(allBlocks, e.blocks...)
+	for _, module := range e.modules {
+		allBlocks = append(allBlocks, module.Blocks...)
+	}
+
+	return allBlocks, nil
+}
+
+// returns true if all evaluations were successful
+func (e *Evaluator) getValuesByBlockType(blockType string) cty.Value {
+
+	blocksOfType := e.blocks.OfType(blockType)
+	values := make(map[string]cty.Value)
+
+	for _, block := range blocksOfType {
+
+		switch block.Type() {
+		case "variable": // variables are special in that their value comes from the "default" attribute
+
+			if block.Label() == "" {
+				continue
+			}
+
+			attributes, _ := block.hclBlock.Body.JustAttributes()
+			if attributes == nil {
+				continue
+			}
+
+			if override, exists := e.inputVars[block.Label()]; exists {
+				values[block.Label()] = override
+			} else if def, exists := attributes["default"]; exists {
+				values[block.Label()], _ = def.Expr.Value(e.ctx)
+			}
+		case "output":
+
+			if block.Label() == "" {
+				continue
+			}
+
+			attributes, _ := block.hclBlock.Body.JustAttributes()
+			if attributes == nil {
+				continue
+			}
+
+			if def, exists := attributes["value"]; exists {
+				func() {
+					defer func() {
+						_ = recover()
+					}()
+					values[block.Label()], _ = def.Expr.Value(e.ctx)
+				}()
+			}
+
+		case "locals":
+			for key, val := range e.readValues(block.hclBlock).AsValueMap() {
+				values[key] = val
+			}
+		case "provider", "module":
+			if block.Label() == "" {
+				continue
+			}
+			values[block.Label()] = e.readValues(block.hclBlock)
+		case "resource", "data":
+
+			if len(block.hclBlock.Labels) < 2 {
+				continue
+			}
+
+			blockMap, ok := values[block.hclBlock.Labels[0]]
+			if !ok {
+				values[block.hclBlock.Labels[0]] = cty.ObjectVal(make(map[string]cty.Value))
+				blockMap = values[block.hclBlock.Labels[0]]
+			}
+
+			valueMap := blockMap.AsValueMap()
+			if valueMap == nil {
+				valueMap = make(map[string]cty.Value)
+			}
+
+			valueMap[block.hclBlock.Labels[1]] = e.readValues(block.hclBlock)
+			values[block.hclBlock.Labels[0]] = cty.ObjectVal(valueMap)
+		}
+
+	}
+
+	return cty.ObjectVal(values)
+
+}
+
+// returns true if all evaluations were successful
+func (e *Evaluator) readValues(block *hcl.Block) cty.Value {
+
+	values := make(map[string]cty.Value)
+
+	attributes, diagnostics := block.Body.JustAttributes()
+	if diagnostics != nil && diagnostics.HasErrors() {
+		return cty.NilVal
+	}
+
+	for _, attribute := range attributes {
+		func() {
+			defer func() {
+				if err := recover(); err != nil {
+					return
+				}
+			}()
+			val, _ := attribute.Expr.Value(e.ctx)
+			values[attribute.Name] = val
+		}()
+	}
+
+	return cty.ObjectVal(values)
+}
diff --git a/internal/app/tfsec/parser/load_blocks.go b/internal/app/tfsec/parser/load_blocks.go
new file mode 100644
index 000000000..8ac9da4c4
--- /dev/null
+++ b/internal/app/tfsec/parser/load_blocks.go
@@ -0,0 +1,21 @@
+package parser
+
+import (
+	"fmt"
+
+	"github.com/hashicorp/hcl/v2"
+)
+
+func LoadBlocksFromFile(file *hcl.File) (hcl.Blocks, error) {
+
+	contents, diagnostics := file.Body.Content(terraformSchema)
+	if diagnostics != nil && diagnostics.HasErrors() {
+		return nil, diagnostics
+	}
+
+	if contents == nil {
+		return nil, fmt.Errorf("file contents is empty")
+	}
+
+	return contents.Blocks, nil
+}
diff --git a/internal/app/tfsec/parser/load_dir.go b/internal/app/tfsec/parser/load_dir.go
new file mode 100644
index 000000000..12314b833
--- /dev/null
+++ b/internal/app/tfsec/parser/load_dir.go
@@ -0,0 +1,43 @@
+package parser
+
+import (
+	"io/ioutil"
+	"path/filepath"
+
+	"github.com/hashicorp/hcl/v2/hclparse"
+
+	"github.com/hashicorp/hcl/v2"
+)
+
+func LoadDirectory(fullPath string) ([]*hcl.File, error) {
+
+	hclParser := hclparse.NewParser()
+
+	fileInfos, err := ioutil.ReadDir(fullPath)
+	if err != nil {
+		return nil, err
+	}
+
+	for _, info := range fileInfos {
+		if info.IsDir() {
+			continue
+		}
+
+		if filepath.Ext(info.Name()) != ".tf" {
+			continue
+		}
+
+		path := filepath.Join(fullPath, info.Name())
+		_, diag := hclParser.ParseHCLFile(path)
+		if diag != nil && diag.HasErrors() {
+			return nil, diag
+		}
+	}
+
+	var files []*hcl.File
+	for _, file := range hclParser.Files() {
+		files = append(files, file)
+	}
+
+	return files, nil
+}
diff --git a/internal/app/tfsec/parser/load_module.go b/internal/app/tfsec/parser/load_module.go
new file mode 100644
index 000000000..f244a4ddd
--- /dev/null
+++ b/internal/app/tfsec/parser/load_module.go
@@ -0,0 +1,113 @@
+package parser
+
+import (
+	"fmt"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"github.com/hashicorp/hcl/v2"
+	"github.com/tfsec/tfsec/internal/app/tfsec/debug"
+	"github.com/zclconf/go-cty/cty"
+)
+
+type ModuleInfo struct {
+	Name       string
+	Path       string
+	Definition *Block
+	Blocks     Blocks
+}
+
+// reads all module blocks and loads the underlying modules, adding blocks to e.moduleBlocks
+func LoadModules(blocks Blocks, moduleBasePath string, metadata *ModulesMetadata) []*ModuleInfo {
+
+	var modules []*ModuleInfo
+
+	for _, moduleBlock := range blocks.OfType("module") {
+		if moduleBlock.Label() == "" {
+			continue
+		}
+		module, err := loadModule(moduleBlock, moduleBasePath, metadata)
+		if err != nil {
+			_, _ = fmt.Fprintf(os.Stderr, "WARNING: Failed to load module: %s\n", err)
+			continue
+		}
+		modules = append(modules, module)
+	}
+
+	return modules
+}
+
+// takes in a module "x" {} block and loads resources etc. into e.moduleBlocks - additionally returns variables to add to ["module.x.*"] variables
+func loadModule(block *Block, moduleBasePath string, metadata *ModulesMetadata) (*ModuleInfo, error) {
+
+	if block.Label() == "" {
+		return nil, fmt.Errorf("module without label at %s", block.Range())
+	}
+
+	var source string
+	attrs, _ := block.hclBlock.Body.JustAttributes()
+	for _, attr := range attrs {
+		if attr.Name == "source" {
+			sourceVal, _ := attr.Expr.Value(&hcl.EvalContext{})
+			if sourceVal.Type() == cty.String {
+				source = sourceVal.AsString()
+			}
+		}
+	}
+
+	if source == "" {
+		return nil, fmt.Errorf("could not read module source attribute at %s", block.Range().String())
+	}
+
+	var modulePath string
+
+	if metadata != nil {
+		// if we have module metadata we can parse all the modules as they'll be cached locally!
+		for _, module := range metadata.Modules {
+			if module.Key == block.Label() || module.Source == source {
+				modulePath = filepath.Clean(filepath.Join(moduleBasePath, module.Dir))
+				break
+			}
+		}
+	}
+
+	if modulePath == "" {
+		// if we have no metadata, we can only support modules available on the local filesystem
+		// users wanting this feature should run a `terraform init` before running tfsec to cache all modules locally
+		if !strings.HasPrefix(source, "./") && !strings.HasPrefix(source, "../") {
+			return nil, fmt.Errorf("missing module with source '%s' -  try to 'terraform init' first", source)
+		}
+
+		modulePath = filepath.Join(filepath.Dir(block.Range().Filename), source)
+	}
+
+	moduleFiles, err := LoadDirectory(modulePath)
+	if err != nil {
+		return nil, fmt.Errorf("failed to load module %s: %w", block.Label(), err)
+	}
+
+	var blocks Blocks
+
+	for _, file := range moduleFiles {
+		fileBlocks, err := LoadBlocksFromFile(file)
+		if err != nil {
+			return nil, err
+		}
+		if len(fileBlocks) > 0 {
+			debug.Log("Added %d blocks from %s...", len(fileBlocks), fileBlocks[0].DefRange.Filename)
+		}
+		for _, fileBlock := range fileBlocks {
+			blocks = append(blocks, NewBlock(fileBlock, nil, block))
+		}
+	}
+
+	debug.Log("Loaded module '%s' (requested at %s)", modulePath, block.Range())
+
+	return &ModuleInfo{
+		Name:       block.Label(),
+		Path:       modulePath,
+		Definition: block,
+		Blocks:     blocks,
+	}, nil
+}
diff --git a/internal/app/tfsec/parser/load_module_metadata.go b/internal/app/tfsec/parser/load_module_metadata.go
new file mode 100644
index 000000000..32073c74b
--- /dev/null
+++ b/internal/app/tfsec/parser/load_module_metadata.go
@@ -0,0 +1,38 @@
+package parser
+
+import (
+	"encoding/json"
+	"os"
+	"path/filepath"
+)
+
+type ModulesMetadata struct {
+	Modules []ModuleMetadata `json:"Modules"`
+}
+
+type ModuleMetadata struct {
+	Key     string `json:"Key"`
+	Source  string `json:"Source"`
+	Version string `json:"Version"`
+	Dir     string `json:"Dir"`
+}
+
+func LoadModuleMetadata(fullPath string) (*ModulesMetadata, error) {
+	metadataPath := filepath.Join(fullPath, ".terraform/modules/modules.json")
+	if _, err := os.Stat(metadataPath); err != nil {
+		return nil, err
+	}
+
+	f, err := os.Open(metadataPath)
+	if err != nil {
+		return nil, err
+	}
+	defer func() { _ = f.Close() }()
+
+	var metadata ModulesMetadata
+	if err := json.NewDecoder(f).Decode(&metadata); err != nil {
+		return nil, err
+	}
+
+	return &metadata, nil
+}
diff --git a/internal/app/tfsec/parser/load_vars.go b/internal/app/tfsec/parser/load_vars.go
new file mode 100644
index 000000000..1338dd6cb
--- /dev/null
+++ b/internal/app/tfsec/parser/load_vars.go
@@ -0,0 +1,35 @@
+package parser
+
+import (
+	"io/ioutil"
+
+	"github.com/hashicorp/hcl/v2/hclsyntax"
+	"github.com/tfsec/tfsec/internal/app/tfsec/debug"
+	"github.com/zclconf/go-cty/cty"
+
+	"github.com/hashicorp/hcl/v2"
+)
+
+func LoadTFVars(filename string) (map[string]cty.Value, error) {
+
+	inputVars := make(map[string]cty.Value)
+
+	if filename == "" {
+		return inputVars, nil
+	}
+
+	src, err := ioutil.ReadFile(filename)
+	if err != nil {
+		return nil, err
+	}
+
+	variableFile, _ := hclsyntax.ParseConfig(src, filename, hcl.Pos{Line: 1, Column: 1})
+	attrs, _ := variableFile.Body.JustAttributes()
+
+	for _, attr := range attrs {
+		debug.Log("Setting '%s' from tfvars file at %s", attr.Name, filename)
+		inputVars[attr.Name], _ = attr.Expr.Value(&hcl.EvalContext{})
+	}
+
+	return inputVars, nil
+}
diff --git a/internal/app/tfsec/parser/parser.go b/internal/app/tfsec/parser/parser.go
index 40359939c..3900e2e0c 100644
--- a/internal/app/tfsec/parser/parser.go
+++ b/internal/app/tfsec/parser/parser.go
@@ -1,451 +1,60 @@
 package parser
 
 import (
-	"fmt"
-	"io/ioutil"
-	"path/filepath"
-	"reflect"
-	"strings"
-
 	"github.com/tfsec/tfsec/internal/app/tfsec/debug"
-
-	"github.com/hashicorp/hcl/v2/hclsyntax"
-
-	"github.com/zclconf/go-cty/cty"
-
-	"github.com/hashicorp/hcl/v2"
-	"github.com/hashicorp/hcl/v2/hclparse"
 )
 
-const maxContextIterations = 32
-
 // Parser is a tool for parsing terraform templates at a given file system location
 type Parser struct {
-	hclParser *hclparse.Parser
-	files     map[string]bool
+	fullPath   string
+	tfvarsPath string
 }
 
 // New creates a new Parser
-func New() *Parser {
+func New(fullPath string, tfvarsPath string) *Parser {
 	return &Parser{
-		hclParser: hclparse.NewParser(),
-		files:     make(map[string]bool),
+		fullPath:   fullPath,
+		tfvarsPath: tfvarsPath,
 	}
 }
 
-type ParseResult struct {
-	Blocks
-	cty.Value
-}
-
-func (parser *Parser) readTFVars(filename string) (map[string]cty.Value, error) {
+// ParseDirectory parses all terraform files within a given directory
+func (parser *Parser) ParseDirectory() (Blocks, error) {
 
-	inputVars := make(map[string]cty.Value)
-
-	if filename == "" {
-		return inputVars, nil
-	}
-
-	src, err := ioutil.ReadFile(filename)
+	debug.Log("Beginning parse for directory '%s'...", parser.fullPath)
+	files, err := LoadDirectory(parser.fullPath)
 	if err != nil {
 		return nil, err
 	}
 
-	tfvars, _ := hclsyntax.ParseConfig(src, filename, hcl.Pos{Line: 1, Column: 1})
-	attrs, _ := tfvars.Body.JustAttributes()
-
-	for _, attr := range attrs {
-		debug.Log("Setting '%s' from tfvars file at %s", attr.Name, filename)
-		inputVars[attr.Name], _ = attr.Expr.Value(&hcl.EvalContext{})
-	}
-
-	return inputVars, nil
-}
-
-// ParseDirectory recursively parses all terraform files within a given directory
-func (parser *Parser) ParseDirectory(path string, excludedDirectories []string, tfvarsPath string) (Blocks, error) {
-
-	debug.Log("Beginning recursive parse of %s...", path)
-	parseCache := newParseCache()
-	if err := parser.recursivelyParseDirectory(path, parseCache, excludedDirectories); err != nil {
-		return nil, err
-	}
-
-	var blocks hcl.Blocks
+	var blocks Blocks
 
-	for _, file := range parser.hclParser.Files() {
-		fileBlocks, err := parser.parseFile(file)
+	for _, file := range files {
+		fileBlocks, err := LoadBlocksFromFile(file)
 		if err != nil {
 			return nil, err
 		}
-		if len(blocks) > 0 {
-			debug.Log("Added %d blocks from %s...", len(blocks), blocks[0].DefRange.Filename)
+		if len(fileBlocks) > 0 {
+			debug.Log("Added %d blocks from %s...", len(fileBlocks), fileBlocks[0].DefRange.Filename)
+		}
+		for _, fileBlock := range fileBlocks {
+			blocks = append(blocks, NewBlock(fileBlock, nil, nil))
 		}
-		blocks = append(blocks, fileBlocks...)
 	}
 
-	inputVars, err := parser.readTFVars(tfvarsPath)
+	debug.Log("Loading TFVars...")
+	inputVars, err := LoadTFVars(parser.tfvarsPath)
 	if err != nil {
 		return nil, err
 	}
 
-	allBlocks, _ := parser.buildEvaluationContext(
-		blocks,
-		path,
-		inputVars,
-		true,
-		parseCache,
-		excludedDirectories,
-	)
-	return allBlocks.RemoveDuplicates(), nil
-}
+	debug.Log("Loading module metadata...")
+	modulesMetadata, _ := LoadModuleMetadata(parser.fullPath)
 
-func (parser *Parser) parseFile(file *hcl.File) (hcl.Blocks, error) {
-
-	contents, diagnostics := file.Body.Content(terraformSchema)
-	if diagnostics != nil && diagnostics.HasErrors() {
-		return nil, diagnostics
-	}
-
-	if contents == nil {
-		return nil, fmt.Errorf("file contents is empty")
-	}
-
-	return contents.Blocks, nil
-}
-
-func (parser *Parser) recursivelyParseDirectory(path string, pc parseCache, excludedDirectories []string) error {
-
-	files, err := ioutil.ReadDir(path)
-	if err != nil {
-		return err
-	}
-FILE:
-	for _, file := range files {
-		if strings.HasPrefix(file.Name(), ".") { //ignore dotfiles (including .terraform!)
-			continue
-		}
-		fullPath := filepath.Join(path, file.Name())
-		if exists := parser.files[fullPath]; exists {
-			continue
-		}
-
-		parser.files[fullPath] = true
-		if file.IsDir() {
-
-			for _, excluded := range excludedDirectories {
-				if fullPath == excluded {
-					continue FILE
-				}
-			}
-
-			// We want local files to be loaded as needed by modules, but
-			// we want to defend against directories being loaded multiple times.
-			if pc.hasSeenPath(fullPath) {
-				continue
-			}
-			pc.addPath(fullPath)
-
-			if err := parser.recursivelyParseDirectory(fullPath, pc, excludedDirectories); err != nil {
-				return err
-			}
-		} else if strings.HasSuffix(file.Name(), ".tf") {
-			_, diagnostics := parser.hclParser.ParseHCLFile(fullPath)
-			if diagnostics != nil && diagnostics.HasErrors() {
-				return diagnostics
-			}
-		}
-	}
-
-	return nil
-}
-
-// BuildEvaluationContext creates an *hcl.EvalContext by parsing values for all terraform variables (where available) then interpolating values into resource, local and data blocks until all possible values can be constructed
-func (parser *Parser) buildEvaluationContext(
-	blocks hcl.Blocks,
-	path string,
-	inputVars map[string]cty.Value,
-	isRoot bool,
-	pc parseCache,
-	excludedDirectories []string,
-) (Blocks, *hcl.EvalContext) {
-	ctx := &hcl.EvalContext{
-		Variables: make(map[string]cty.Value),
-		Functions: Functions(path),
-	}
-
-	ctx.Variables["module"] = cty.ObjectVal(make(map[string]cty.Value))
-
-	moduleBlocks := make(map[string]Blocks)
-
-	debug.Log("Beginning evaluation...")
-
-	var lastContext hcl.EvalContext
-
-	for i := 0; i < maxContextIterations; i++ {
-
-		debug.Log("Starting iteration %d of context evaluation...", i+1)
-
-		ctx.Variables["var"] = parser.getValuesByBlockType(ctx, blocks, "variable", inputVars)
-		ctx.Variables["local"] = parser.getValuesByBlockType(ctx, blocks, "locals", nil)
-		ctx.Variables["provider"] = parser.getValuesByBlockType(ctx, blocks, "provider", nil)
-		resources := parser.getValuesByBlockType(ctx, blocks, "resource", nil)
-		for key, resource := range resources.AsValueMap() {
-			ctx.Variables[key] = resource
-		}
-		ctx.Variables["data"] = parser.getValuesByBlockType(ctx, blocks, "data", nil)
-
-		if isRoot {
-			ctx.Variables["output"] = parser.getValuesByBlockType(ctx, blocks, "output", nil)
-		} else {
-			outputs := parser.getValuesByBlockType(ctx, blocks, "output", nil)
-			for key, val := range outputs.AsValueMap() {
-				ctx.Variables[key] = val
-			}
-		}
-
-		for _, moduleBlock := range blocks.OfType("module") {
-			if len(moduleBlock.Labels) == 0 {
-				continue
-			}
-			moduleMap := ctx.Variables["module"].AsValueMap()
-			if moduleMap == nil {
-				moduleMap = make(map[string]cty.Value)
-			}
-			moduleName := moduleBlock.Labels[0]
-
-			result, nameValue := parser.parseModuleBlock(moduleBlock, ctx, path, pc, excludedDirectories) // todo return parsed blocks here too
-			for _, block := range result {
-				block.inModule = true
-			}
-			moduleBlocks[moduleName] = result
-			moduleMap[moduleName] = nameValue
-			ctx.Variables["module"] = cty.ObjectVal(moduleMap)
-		}
-
-		// if ctx matches the last evaluation, we can bail, nothing left to resolve
-		if reflect.DeepEqual(lastContext.Variables, ctx.Variables) {
-			break
-		}
-
-		lastContext.Variables = make(map[string]cty.Value)
-		for k, v := range ctx.Variables {
-			lastContext.Variables[k] = v
-		}
-	}
-
-	var localBlocks []*Block
-	for _, block := range blocks {
-		localBlocks = append(localBlocks, NewBlock(block, ctx))
-	}
-
-	for moduleName, blocks := range moduleBlocks {
-		for _, block := range blocks {
-			block.prefix = fmt.Sprintf("module.%s", moduleName)
-			localBlocks = append(localBlocks, block)
-		}
-	}
-
-	return localBlocks, ctx
-}
-
-func (parser *Parser) parseModuleBlock(
-	block *hcl.Block,
-	parentContext *hcl.EvalContext,
-	rootPath string,
-	pc parseCache,
-	excludedDirectories []string,
-) (Blocks, cty.Value) {
-
-	if len(block.Labels) == 0 {
-		return nil, cty.NilVal
-	}
-
-	inputVars := make(map[string]cty.Value)
-
-	var source string
-	attrs, _ := block.Body.JustAttributes()
-	for _, attr := range attrs {
-
-		inputVars[attr.Name], _ = attr.Expr.Value(parentContext)
-
-		if attr.Name == "source" {
-			sourceVal, _ := attr.Expr.Value(parentContext)
-			if sourceVal.Type() == cty.String {
-				source = sourceVal.AsString()
-			}
-		}
-	}
-
-	if source == "" {
-		return nil, cty.NilVal
-	}
-
-	if !strings.HasPrefix(source, "./") && !strings.HasPrefix(source, "../") {
-		// TODO support module registries/github etc.
-		return nil, cty.NilVal
-	}
-
-	path := filepath.Join(rootPath, source)
-	if result, ok := pc.lookupResult(path); ok {
-		return result.Blocks, result.Value
-	}
-
-	// We need to respect the module's path if it's local to the filesystem.
-	// If the `rootPath` != `modulePath` then this means that we're not
-	// parsing this module from the correct working directory, and so
-	// parsing will break. In that case, reset the path to the path known
-	// to the module so that local paths will work as expected.
-	modulePath := filepath.Dir(block.DefRange.Filename)
-	if rootPath != modulePath {
-		path = modulePath
-	}
-
-	subParser := New()
-
-	if err := subParser.recursivelyParseDirectory(path, pc, excludedDirectories); err != nil {
-		return nil, cty.NilVal
-	}
-
-	var blocks []*hcl.Block
-
-	for _, file := range subParser.hclParser.Files() {
-		fileBlocks, err := subParser.parseFile(file)
-		if err != nil {
-			return nil, cty.NilVal
-		}
-		blocks = append(blocks, fileBlocks...)
-	}
-
-	childModules, ctx := subParser.buildEvaluationContext(blocks, path, inputVars, false, pc, excludedDirectories)
-	parseResult := ParseResult{
-		Blocks: childModules.RemoveDuplicates(),
-		Value:  cty.ObjectVal(ctx.Variables),
-	}
-
-	pc.storeResult(path, parseResult)
-	return parseResult.Blocks, parseResult.Value
-}
-
-// returns true if all evaluations were successful
-func (parser *Parser) readValues(ctx *hcl.EvalContext, block *hcl.Block) cty.Value {
-
-	values := make(map[string]cty.Value)
-
-	attributes, diagnostics := block.Body.JustAttributes()
-	if diagnostics != nil && diagnostics.HasErrors() {
-		return cty.NilVal
-	}
-
-	for _, attribute := range attributes {
-		val, _ := attribute.Expr.Value(ctx)
-		values[attribute.Name] = val
-	}
-
-	return cty.ObjectVal(values)
-}
-
-// returns true if all evaluations were successful
-func (parser *Parser) getValuesByBlockType(ctx *hcl.EvalContext, blocks hcl.Blocks, blockType string, inputVars map[string]cty.Value) cty.Value {
-
-	blocksOfType := blocks.OfType(blockType)
-	values := make(map[string]cty.Value)
-
-	for _, block := range blocksOfType {
-
-		switch block.Type {
-		case "variable": // variables are special in that their value comes from the "default" attribute
-			if len(block.Labels) < 1 {
-				continue
-			}
-			attributes, _ := block.Body.JustAttributes()
-			if attributes == nil {
-				continue
-			}
-			if override, exists := inputVars[block.Labels[0]]; exists {
-				values[block.Labels[0]] = override
-			} else if def, exists := attributes["default"]; exists {
-				values[block.Labels[0]], _ = def.Expr.Value(ctx)
-			}
-		case "output":
-			if len(block.Labels) < 1 {
-				continue
-			}
-			attributes, _ := block.Body.JustAttributes()
-			if attributes == nil {
-				continue
-			}
-			if def, exists := attributes["value"]; exists {
-				values[block.Labels[0]], _ = def.Expr.Value(ctx)
-			}
-		case "locals":
-			for key, val := range parser.readValues(ctx, block).AsValueMap() {
-				values[key] = val
-			}
-		case "provider", "module":
-			if len(block.Labels) < 1 {
-				continue
-			}
-			values[block.Labels[0]] = parser.readValues(ctx, block)
-		case "resource", "data":
-
-			if len(block.Labels) < 2 {
-				continue
-			}
-
-			blockMap, ok := values[block.Labels[0]]
-			if !ok {
-				values[block.Labels[0]] = cty.ObjectVal(make(map[string]cty.Value))
-				blockMap = values[block.Labels[0]]
-			}
-
-			valueMap := blockMap.AsValueMap()
-			if valueMap == nil {
-				valueMap = make(map[string]cty.Value)
-			}
-
-			valueMap[block.Labels[1]] = parser.readValues(ctx, block)
-			values[block.Labels[0]] = cty.ObjectVal(valueMap)
-		}
-
-	}
-
-	return cty.ObjectVal(values)
-
-}
-
-// pathBreaker is a set of known paths to allow us to implement circuit breaking
-// so that we can defend against infinity recursion in specific module
-// sourcing circumstances.
-
-type parseCache struct {
-	visitedPaths map[string]struct{}
-	results      map[string]ParseResult
-}
-
-func newParseCache() parseCache {
-	return parseCache{
-		visitedPaths: make(map[string]struct{}),
-		results:      make(map[string]ParseResult),
-	}
-}
-
-// add adds a new, now, known path to our path circuit breaker.
-func (p parseCache) addPath(path string) {
-	p.visitedPaths[path] = struct{}{}
-}
-
-// hasSeen returns a boolean denoting if we've seen the given path before.
-func (p parseCache) hasSeenPath(path string) bool {
-	_, ok := p.visitedPaths[path]
-	return ok
-}
-
-func (p parseCache) lookupResult(fullPath string) (ParseResult, bool) {
-	result, ok := p.results[fullPath]
-	return result, ok
-}
+	debug.Log("Loading modules...")
+	modules := LoadModules(blocks, parser.fullPath, modulesMetadata)
 
-func (p parseCache) storeResult(fullPath string, result ParseResult) {
-	p.results[fullPath] = result
+	debug.Log("Evaluating expressions...")
+	evaluator := NewEvaluator(parser.fullPath, blocks, inputVars, modulesMetadata, modules)
+	return evaluator.EvaluateAll()
 }
diff --git a/internal/app/tfsec/parser/range.go b/internal/app/tfsec/parser/range.go
index d477f821f..fca748b48 100644
--- a/internal/app/tfsec/parser/range.go
+++ b/internal/app/tfsec/parser/range.go
@@ -10,10 +10,7 @@ type Range struct {
 }
 
 // String creates a human-readable summary of the range
-func (r *Range) String() string {
-	if r == nil {
-		return "unknown"
-	}
+func (r Range) String() string {
 	if r.StartLine != r.EndLine {
 		return fmt.Sprintf("%s:%d-%d", r.Filename, r.StartLine, r.EndLine)
 	}
diff --git a/internal/app/tfsec/scanner/check.go b/internal/app/tfsec/scanner/check.go
index cadf27c68..cf4d5482e 100644
--- a/internal/app/tfsec/scanner/check.go
+++ b/internal/app/tfsec/scanner/check.go
@@ -3,7 +3,9 @@ package scanner
 import (
 	"fmt"
 	"os"
+	"runtime/debug"
 
+	internalDebug "github.com/tfsec/tfsec/internal/app/tfsec/debug"
 	"github.com/zclconf/go-cty/cty"
 
 	"github.com/tfsec/tfsec/internal/app/tfsec/parser"
@@ -59,7 +61,8 @@ type CheckDocumentation struct {
 func (check *Check) Run(block *parser.Block, context *Context) []Result {
 	defer func() {
 		if err := recover(); err != nil {
-			fmt.Fprintf(os.Stderr, "WARNING: skipped %s check due to error(s): %s\n", check.Code, err)
+			_, _ = fmt.Fprintf(os.Stderr, "WARNING: skipped %s check due to error(s): %s\n", check.Code, err)
+			internalDebug.Log("Stack trace for failed %s check:\n%s\n\n", check.Code, string(debug.Stack()))
 		}
 	}()
 	results := check.CheckFunc(check, block, context)
@@ -144,5 +147,6 @@ func (check *Check) NewResultWithValueAnnotation(description string, r parser.Ra
 		Description:     description,
 		Range:           r,
 		RangeAnnotation: fmt.Sprintf("[%s] %#v", typeStr, raw),
+		Severity:        severity,
 	}
 }
diff --git a/internal/app/tfsec/scanner/scanner.go b/internal/app/tfsec/scanner/scanner.go
index 09f4fd346..b2c91c71d 100644
--- a/internal/app/tfsec/scanner/scanner.go
+++ b/internal/app/tfsec/scanner/scanner.go
@@ -37,7 +37,7 @@ func (scanner *Scanner) Scan(blocks []*parser.Block, excludedChecksList []string
 	for _, block := range blocks {
 		for _, check := range GetRegisteredChecks() {
 			if check.IsRequiredForBlock(block) {
-				debug.Log("Running check for %s on %s.%s (%s)...", check.Code, block.Type(), block.Name(), block.Range().Filename)
+				debug.Log("Running check for %s on %s.%s (%s)...", check.Code, block.Type(), block.FullName(), block.Range().Filename)
 				for _, result := range check.Run(block, context) {
 					if !scanner.checkRangeIgnored(result.RuleID, result.Range) && !checkInList(result.RuleID, excludedChecksList) {
 						results = append(results, result)
diff --git a/internal/app/tfsec/security/sensitive.go b/internal/app/tfsec/security/sensitive.go
index 2566a4cc3..4697862af 100644
--- a/internal/app/tfsec/security/sensitive.go
+++ b/internal/app/tfsec/security/sensitive.go
@@ -6,8 +6,6 @@ import (
 
 func IsSensitiveAttribute(name string) bool {
 
-	// TODO add a whole bunch of regular expressions in here
-
 	name = strings.ToLower(name)
 
 	switch {


Test patch:
diff --git a/internal/app/tfsec/parser/block_test.go b/internal/app/tfsec/parser/block_test.go
index a4e269f04..86b62ca18 100644
--- a/internal/app/tfsec/parser/block_test.go
+++ b/internal/app/tfsec/parser/block_test.go
@@ -1,9 +1,10 @@
 package parser
 
 import (
-	"github.com/stretchr/testify/assert"
 	"path/filepath"
 	"testing"
+
+	"github.com/stretchr/testify/assert"
 )
 
 func Test_IsPresentCheckOnBlock(t *testing.T) {
@@ -95,7 +96,7 @@ resource "aws_s3_bucket" "my-bucket" {
 
 func createBlocksFromSource(source string) []*Block {
 	path := createTestFile("test.tf", source)
-	blocks, err := New().ParseDirectory(filepath.Dir(path), nil, "")
+	blocks, err := New(filepath.Dir(path), "").ParseDirectory()
 	if err != nil {
 		panic(err)
 	}
diff --git a/internal/app/tfsec/parser/parser_test.go b/internal/app/tfsec/parser/parser_test.go
index 1f1307f91..617bdb67a 100644
--- a/internal/app/tfsec/parser/parser_test.go
+++ b/internal/app/tfsec/parser/parser_test.go
@@ -7,6 +7,8 @@ import (
 	"sort"
 	"testing"
 
+	"github.com/tfsec/tfsec/internal/app/tfsec/debug"
+
 	"github.com/zclconf/go-cty/cty"
 
 	"github.com/stretchr/testify/assert"
@@ -15,7 +17,6 @@ import (
 )
 
 func Test_BasicParsing(t *testing.T) {
-	parser := New()
 
 	path := createTestFile("test.tf", `
 
@@ -48,7 +49,9 @@ data "cats_cat" "the-cats-mother" {
 
 `)
 
-	blocks, err := parser.ParseDirectory(filepath.Dir(path), nil, "")
+	debug.Enabled = true
+	parser := New(filepath.Dir(path), "")
+	blocks, err := parser.ParseDirectory()
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -129,9 +132,8 @@ output "result" {
 		"module",
 	)
 
-	parser := New()
-
-	blocks, err := parser.ParseDirectory(path, nil, "")
+	parser := New(path, "")
+	blocks, err := parser.ParseDirectory()
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -140,7 +142,7 @@ output "result" {
 	require.Len(t, modules, 1)
 	module := modules[0]
 	assert.Equal(t, "module", module.Type())
-	assert.Equal(t, "module.my-mod", module.Name())
+	assert.Equal(t, "module.my-mod", module.FullName())
 	inputAttr := module.GetAttribute("input")
 	require.NotNil(t, inputAttr)
 	require.Equal(t, cty.String, inputAttr.Value().Type())
@@ -149,10 +151,10 @@ output "result" {
 	outputs := blocks.OfType("output")
 	require.Len(t, outputs, 2)
 	for _, output := range outputs {
-		if output.inModule {
-			assert.Equal(t, "module.my-mod.result", output.Name())
+		if output.moduleBlock != nil {
+			assert.Equal(t, "module.my-mod:output.result", output.FullName())
 		} else {
-			assert.Equal(t, "output.result", output.Name())
+			assert.Equal(t, "output.result", output.FullName())
 		}
 		valAttr := output.GetAttribute("value")
 		require.NotNil(t, valAttr)
@@ -186,9 +188,8 @@ output "result" {
 		"",
 	)
 
-	parser := New()
-
-	blocks, err := parser.ParseDirectory(path, nil, "")
+	parser := New(path, "")
+	blocks, err := parser.ParseDirectory()
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -197,7 +198,7 @@ output "result" {
 	require.Len(t, modules, 1)
 	module := modules[0]
 	assert.Equal(t, "module", module.Type())
-	assert.Equal(t, "module.my-mod", module.Name())
+	assert.Equal(t, "module.my-mod", module.FullName())
 	inputAttr := module.GetAttribute("input")
 	require.NotNil(t, inputAttr)
 	require.Equal(t, cty.String, inputAttr.Value().Type())
@@ -206,10 +207,10 @@ output "result" {
 	outputs := blocks.OfType("output")
 	require.Len(t, outputs, 2)
 	for _, output := range outputs {
-		if output.inModule {
-			assert.Equal(t, "module.my-mod.result", output.Name())
+		if output.moduleBlock != nil {
+			assert.Equal(t, "module.my-mod:output.result", output.FullName())
 		} else {
-			assert.Equal(t, "output.result", output.Name())
+			assert.Equal(t, "output.result", output.FullName())
 		}
 		valAttr := output.GetAttribute("value")
 		require.NotNil(t, valAttr)
diff --git a/internal/app/tfsec/test/module_scan_test.go b/internal/app/tfsec/test/module_scan_test.go
index bf5728035..f48591690 100644
--- a/internal/app/tfsec/test/module_scan_test.go
+++ b/internal/app/tfsec/test/module_scan_test.go
@@ -35,7 +35,7 @@ resource "problem" "uhoh" {
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
 			path := createTestFileWithModule(test.source, test.moduleSource)
-			blocks, err := parser.New().ParseDirectory(path, nil, "")
+			blocks, err := parser.New(path, "").ParseDirectory()
 			if err != nil {
 				t.Fatal(err)
 			}
diff --git a/internal/app/tfsec/test/setup_test.go b/internal/app/tfsec/test/setup_test.go
index 2468710d2..859fb56a6 100644
--- a/internal/app/tfsec/test/setup_test.go
+++ b/internal/app/tfsec/test/setup_test.go
@@ -59,7 +59,7 @@ func scanSource(source string) []scanner.Result {
 
 func createBlocksFromSource(source string) []*parser.Block {
 	path := createTestFile("test.tf", source)
-	blocks, err := parser.New().ParseDirectory(filepath.Dir(path), nil, "")
+	blocks, err := parser.New(filepath.Dir(path), "").ParseDirectory()
 	if err != nil {
 		panic(err)
 	}