> TheAuditor / blog
iac, terraform, aws, taint

From a React Form to an S3 Bucket: Closing the App-to-Infrastructure Gap

Application code and infrastructure code live in different repos, get scanned by different tools, and stay disconnected — until the bash script that bridges them quietly passes a tainted value into terraform apply -var.

Picture your internal deployment dashboard. It has a form. One of the fields is an ACL dropdown — public-read, private, authenticated-read. By the time that value lands in your AWS account, every tool in your pipeline has forgotten it ever came from a user.

That gap is what most AppSec programs miss.

The chain everyone has, nobody traces

The minimum-viable-deploy stack you ship in a year-one startup:

  1. A React or Vue form. The user picks aclSetting.
  2. A Java or Node API. Receives @RequestBody ConfigRequest.
  3. A bash deploy script. Got invoked via ProcessBuilder or child_process.exec.
  4. Terraform or CDK. The deploy script ran terraform apply -var "acl_setting=$1".
  5. AWS. The resulting aws_s3_bucket has acl = var.acl_setting.

Five layers. Three languages. One configuration setting that crosses every layer. Today, in 2026:

  • Your SAST tool scans the Java code. It sees aclSetting come in, watches it flow into ProcessBuilder, flags “command injection”, and stops.
  • Your IaC linter scans s3.tf. It sees acl = var.acl_setting and notices the value is unbounded. It might flag “ACL not validated.”
  • Your container scanner looks at the docker build. It scans for CVEs in base images.
  • Nobody connects the React form to the S3 bucket. Three different reports, in three different dashboards, owned by three different on-call rotations.

When the bug ships — and someone selects public-read from a poorly designed admin UI, or the form gets tampered with via CSRF, or the dropdown gets repurposed for a different feature without re-thinking the validation — the forensics team is usually the one who eventually connects React to S3. After the bucket leaks. After the post-mortem.

What it looks like when the chain is traced

Drop the engine in the repo that contains the React app, the Java API, the bash scripts, and the Terraform module. Run aud full --offline. Open the resulting database. There is the trace, end to end:

SOURCE: React form         aclSetting input field (entry_point)
  -> HTTP boundary         axios.post -> @RequestBody
  -> Java service          ConfigService.updateConfig(aclSetting, ...)
  -> Subprocess boundary   ProcessBuilder("bash", "wrapper.sh", ...)
  -> Bash variable         $4 -> ACL_SETTING
  -> CLI boundary          terraform apply -var "acl_setting=$ACL_SETTING"
  -> Terraform variable    variable "acl_setting" {}
  -> Infrastructure flow   resource "aws_s3_bucket" { acl = var.acl_setting }
SINK: AWS S3 public ACL    Infrastructure Public Exposure

Every line is a named hop. Every boundary type is identified. Every file and line lives in the database. A human investigator — or an AI agent — can ask “where does this React field end up?” and get this entire chain back in one query.

The IaC sinks we recognize

PatternRisk
Tainted value flows into aws_s3_bucket.aclPublic Exposure
Tainted value flows into aws_security_group CIDROpen Network Exposure
Wildcard "*" in an aws_iam_policy_documentWildcard IAM
Resource name interpolated from user inputResource Injection
encrypted = false or missing encryption attributeUnencrypted Resource

These are not surface-level lint rules. The engine knows whether the value reaching them is sourced from a user input two or three layers back. A hard-coded "public-read" in a constants file is not the same finding as aclSetting flowing in from a form, and the trace makes the difference visible.

Coverage today: Terraform / HCL and AWS CDK (both Python and TypeScript flavors). GitHub Actions workflows are also indexed — workflow_dispatch inputs feed taint forward, and a PR title that flows into a TF_VAR_* is traced just as carefully.

What this changes for cloud security review

Audit weeks shrink. Cross-team finger-pointing shrinks. The question stops being “is this Terraform resource configured correctly?” and becomes “is the value reaching this resource sourced from anywhere a user controls?” — which is the question that actually matters.

Static review of Terraform alone tells you the shape of the bucket. Cross-language taint tells you whether someone can reach into the bucket from the front door.

Honest disclaimers

The Terraform support traces named var.X references and the standard top-level resource attribute set. It does not currently execute full Terraform module / for_each / locals evaluation. If the dangerous value reaches the sink through a complex module composition, the chain may stop at the module boundary. The common cases — variable in, attribute out — work today. We do not claim to replace terraform plan.

The binary is pre-launch. We ship when the compiled artifact clears the same OWASP corpora the source already does.

What is next

Subscribe via the signup form for launch notifications. We only email when there is something real to share.