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:
- A React or Vue form. The user picks
aclSetting. - A Java or Node API. Receives
@RequestBody ConfigRequest. - A bash deploy script. Got invoked via
ProcessBuilderorchild_process.exec. - Terraform or CDK. The deploy script ran
terraform apply -var "acl_setting=$1". - AWS. The resulting
aws_s3_buckethasacl = 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
aclSettingcome in, watches it flow intoProcessBuilder, flags “command injection”, and stops. - Your IaC linter scans
s3.tf. It seesacl = var.acl_settingand 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
| Pattern | Risk |
|---|---|
Tainted value flows into aws_s3_bucket.acl | Public Exposure |
Tainted value flows into aws_security_group CIDR | Open Network Exposure |
Wildcard "*" in an aws_iam_policy_document | Wildcard IAM |
| Resource name interpolated from user input | Resource Injection |
encrypted = false or missing encryption attribute | Unencrypted 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.