Pre-Beta Pre-beta signups are open now. The full audytx engine is live for pre-beta. Everything free today stays free — paid tiers arrive Sep 01, 2026 from $20/month for unlimited repositories.
False positives · context-aware scanning · benchmark

Checkov false positives — why audytx has 36× fewer

Most Terraform scanners check each resource in isolation, so clean infrastructure lights up with hundreds of findings — a Lambda flagged for a missing dead-letter queue even when nothing async ever invokes it. audytx pre-computes a graph of how your resources actually connect and flags only what context can't explain away — every suppressed finding stays visible with its rationale, never dropped silently.

Measured

The numbers

Measured against three other scanners on one corpus of clean, well-maintained AWS modules.

36×
fewer false positives than Checkov
33 vs 1,193 · 21 clean modules
20+
cross-resource reasoning axes
pre-computed once per scan
fewer false positives than Trivy
33 vs 175 · same corpus
The root cause

Why single-resource scanners produce so many false positives

A scanner that checks each resource in isolation has to fire every check that could apply — it can't see the context that makes the check irrelevant. Five patterns audytx suppresses, and the noise a single-resource scanner emits instead.

1. Lambda DLQ pattern

The check: "Lambda function has no dead-letter queue"
Checkov: aws_lambda_function.api — Lambda DLQ missing ⚠
audytx: suppressed — this Lambda is only invoked synchronously via API Gateway. A DLQ only fires on async invocations; there are none here.

2. DLQ-needs-a-DLQ pattern

The check: "SQS queue has no dead-letter queue"
Checkov: aws_sqs_queue.jobs_dlq — Queue has no DLQ of its own ⚠
audytx: suppressed — this queue is the dead-letter target of aws_sqs_queue.jobs. Requiring a DLQ to have its own DLQ is infinite regress.

3. Ephemeral data pattern

The check: "DynamoDB table does not have point-in-time recovery enabled"
Checkov: aws_dynamodb_table.sessions — PITR not enabled ⚠
audytx: suppressed — TTL is configured on this table; the data is intentionally ephemeral. Point-in-time recovery is mismatched for data that self-expires.

4. Internal-load-balancer pattern

The check: "Load balancer does not have deletion protection enabled"
Checkov: aws_lb.internal_admin — Deletion protection disabled ⚠
audytx: suppressed — this is an internal load balancer with no internet route. Deletion protection matters most for public-facing LBs; internal-only LBs carry lower risk.

5. IMDSv2 inheritance pattern

The check: "EC2 instance does not enforce IMDSv2"
Checkov: aws_instance.worker — IMDSv2 not enforced ⚠
audytx: suppressed — IMDSv2 is enforced at the account level; this instance inherits that setting. No per-instance override is needed.
How it decides

The reasoning checks

Each check is a class of relationship that can prove a finding benign — computed once per scan across your full resource graph. The rule still fires; the context decides whether it survives.

Queue is already a dead-letter target
A queue that is itself a dead-letter destination doesn't need its own DLQ — there's nowhere further to send a failure.
Lambda is invoked synchronously
Dead-letter queues only fire on async invocations. When a Lambda is only invoked synchronously (API Gateway, ALB, Step Functions), a DLQ would never receive an event.
Data is intentionally short-lived
Tables, queues, and buckets with a configured expiry policy are ephemeral by design — point-in-time recovery and versioning are mismatched for data that self-expires.
Resource has no internet exposure
Internal load balancers and VPC-only resources are lower-risk for exposure-related findings — hardening advice aimed at public-facing resources doesn't always apply.
Encryption method satisfies the check's intent
Server-side encryption with a managed key satisfies at-rest encryption findings even when the check nominally demands a customer-managed KMS key. The data is protected, which is what the check is for.
Role's trust is narrowly scoped
An IAM role whose trust policy limits assumption to a specific service or account has a reduced blast radius — findings about its permissions are weighted accordingly.
Role's actual permissions, after expansion
Wildcard actions are expanded to concrete permissions before risk scoring. A role with a broad action wildcard is treated differently from one with a handful of specific read actions.
Instance inherits account-level settings
When a metadata security setting (like requiring IMDSv2) is enforced at the account or launch-template level, individual instances inherit it without needing a per-instance override.
Resource is tagged as non-production
Resources explicitly tagged as development or staging may be suppressed for production-hardening findings that don't apply at that lifecycle stage.
Queue handles errors at the source-mapping level
When an SQS event source mapping is configured with its own error handling, the Lambda function DLQ is redundant — failures are already caught before they'd reach the function.
Secret is automatically rotated
A Secrets Manager secret with rotation configured is not a static credential — findings about long-lived secrets don't apply when the secret regularly changes itself.
Public access is blocked at a higher level
When public access is blocked at the account level, individual bucket public-access findings are already covered — the stricter setting wins regardless of bucket-level configuration.
Encryption key scope doesn't cover sensitive data
A KMS key used exclusively by non-sensitive services may be suppressed for rotation requirements written on the assumption the key protects sensitive customer data.
Database runs across multiple availability zones
Multi-AZ database deployments already address the single-point-of-failure concern that some backup-window findings are trying to prevent.
Resource emits to a connected log group
When a resource is wired to a CloudWatch log group, logging findings are satisfied even if the per-resource logging flag isn't explicitly set — the logs are flowing.
Network traffic is logged at the VPC level
When flow logs are enabled on the VPC a resource belongs to, network visibility findings on that resource are already addressed by the parent network's logging.
Container uses managed runtime (Fargate)
Fargate containers run on managed infrastructure that doesn't support custom host networking or privileged mode — findings about those EC2-specific risks don't apply.
Shown, not dropped

How a suppression looks in the PR comment

Suppressed findings are never silently dropped — they land in a collapsible block in the PR comment, each with a plain-English rationale you can audit.

🧠 audytx reasoned about 4 findings and chose not to flag them
· aws_lambda_function.api — DLQ not needed: this Lambda is only invoked synchronously via API Gateway; a DLQ would never receive an event
· aws_sqs_queue.jobs_dlqthis queue is itself the dead-letter target for jobs_queue; requiring a DLQ to have its own DLQ is infinite regress
· aws_dynamodb_table.sessions — PITR skipped: TTL is configured — this table is intentionally ephemeral; point-in-time recovery is mismatched for data that self-expires
· aws_lb.internal_admin — deletion protection lower risk: this is an internal load balancer with no internet route; the finding targets public-facing LBs

Disagree with a call? Override it in a .audytx-baseline.yaml file in your repo — see baseline suppression in the docs.

The benchmark

The benchmark: 21 clean production modules

All four tools ran against 21 well-maintained AWS community modules with an expected HIGH finding count of 0 — every finding here is noise, so lower is better. Full data and methodology: the benchmark page.

ModuleaudytxCheckovTrivyKICS
terraform-aws-iam328710
terraform-aws-ecs086162
terraform-aws-lambda6112237
terraform-aws-rds212471
terraform-aws-alb454132
terraform-aws-eks688381
terraform-aws-s3-bucket3129184
+ 14 more modules93135917
Total (21 modules)331,19317534
KICS reaches 34 (close to audytx's 33) only by detecting 3% of IAM privilege-escalation paths in Table 1 of the benchmark (1 of 31), to audytx's 100%. audytx achieves its low false-positive count without sacrificing IAM recall.
Open beta

Start free during the open beta

Install audytx on one repo, open a PR that touches your ECS, Lambda, or IAM resources, and read the reasoning block on the first scan.

Structural detection

Secrets detection without entropy guessing

audytx flags a hardcoded credential only when its format proves it — fixed vendor prefixes and unambiguous key material. It skips entropy scoring entirely, so UUIDs, commit hashes, and key IDs never turn into triage work.

Flagged — the format proves it

AWS access key IDs Stripe live keys Slack tokens PEM / OpenPGP private keys GCP service-account keys Azure Storage connection strings

Skipped — no format proof

UUIDs git commit hashes KMS key IDs encoded certificate blobs random-looking strings

Every pattern is grounded in the vendor's own published key-format documentation — the same discipline the reasoning layer applies to suppression: prove it, or stay silent.

FAQ

Questions teams ask

Doesn't suppression hide real issues?

No — suppressed findings are never removed. They appear in a dedicated collapsible block in every PR comment, each with a plain-English explanation of why it was dismissed. You can always see what was reasoned away and why, and override any suppression in your .audytx-baseline.yaml.

How do I override a suppression I disagree with?

Add an entry to .audytx-baseline.yaml in your repo root specifying the rule ID and resource. audytx will stop suppressing that finding — it'll surface as a real finding on the next PR that touches the resource. You can set an expiry date so the override is time-bounded.

What if audytx is wrong about the context?

The reasoning checks are conservative — audytx only suppresses when the relationship in your Terraform configuration is unambiguous. If the configuration is incomplete or the connection can't be proven (e.g. the DLQ reference is behind a variable), the finding is kept. Incomplete evidence = no suppression.

Why does Checkov have 1,193 false positives on clean modules?

Checkov applies every check that matches a resource type, without asking whether the finding is warranted by the resource's actual role in the system. It has more checks (breadth) and less context reasoning (precision). On IAM privilege escalation, both tools reach full recall (100% of the 31 documented paths in the BishopFox iam-vulnerable corpus), and audytx does it at the highest precision of any tool with full recall. The gap between the two tools is entirely in clean-infrastructure precision.

Does audytx support suppression files like Checkov's .checkov.yaml?

Yes, via .audytx-baseline.yaml. You list rule IDs to suppress, optionally scoped to specific files or resources, with an expiry date. This is for cases the reasoning axes can't cover — team-specific decisions, known acceptable risks, or third-party modules you can't change.

Stop triaging false positives

Install audytx on one repo. Open a PR that touches your ECS, Lambda, or IAM resources. See the reasoning block — and how many findings don't make it through.

Install audytx free →

See the full benchmark: audytx vs Checkov, Trivy, KICS, Terrascan →