Skip to content

feat: entropy-based secret detection (kind: high_entropy_string + SEC-SECRETS-ENTROPY-001) - #34

Open
ChrisAdkin8 wants to merge 1 commit into
mainfrom
feat/entropy-secret-detection
Open

ChrisAdkin8 wants to merge 1 commit into
mainfrom
feat/entropy-secret-detection

Conversation

@ChrisAdkin8

@ChrisAdkin8 ChrisAdkin8 commented May 30, 2026 •

Copy link
Copy Markdown
Owner

The audit's #1 capability quick-win: pure-stdlib entropy-based secret
detection that complements the existing name-based grep secret rules.
Those only fire on password/secret/api_key-style fields; this catches a
high-entropy token dropped into any argument, regardless of its name.

New pattern kind: high_entropy_string

scripts/_handlers_security.py. For each resource body (comments stripped first
— length-preserving, so line numbers stay accurate) it scans name = "literal"
assignments and flags values that are:

  • charset-gated to a base64/token alphabet,
  • length-bounded (20–100),
  • mix ≥2 character classes (lower/upper/digit), and
  • clear a Shannon entropy threshold (default 4.0 bits/char).

All three thresholds are catalogue-configurable (min_length / max_length /
min_entropy).

Why these thresholds (calibrated empirically, not guessed)

Measured real secrets vs. common Terraform non-secrets:

  • base64 charset + H≥4.0 cleanly separates genuine tokens (AWS secret 4.61,
    GitHub PAT 4.14, random 4.58) from hex strings / git SHAs / image
    digests
    — which use only 16 symbols so top out at H≈3.7, below the
    bar. So v1 needs no separate hex handling and dodges the git-SHA
    false-positive class entirely.
  • excludes interpolations/refs (${…}, var.*), cloud resource-ids
    (ami-/vpc-/…), and single-character-class kebab/snake names
    (my-app-prod-logs-bucket) that can clear the entropy bar on length alone.
  • max_length drops long base64 blobs (certs / user_data) that aren't tokens.

Rule

SEC-SECRETS-ENTROPY-001 — HIGH, CWE-798/CWE-259, MITRE T1552.001. A
heuristic, so findings ask the operator to verify + rotate; no fix_hcl (you
can't auto-fix a leaked secret).

Verification

  • tests/test_entropy_secrets.py — 23 tests (entropy math, classifier
    exclusions incl. the kebab-name FP a test caught during dev, handler
    location/line/threshold/comment-awareness).
  • self_test 262/262 clean + dirty fixture fires / clean fixture silent.
  • Terragoat unchanged (the rule adds zero findings there — conservative by
    design, so no snapshot/CI-tolerance churn).
  • Full suite 1251 passed, schema PASS, strict-load clean, attack-drift in
    sync, both doc-drift gates clean. Catalogue 353 → 354 (rule docs regenerated;
    catalog/README + docs/custom-rules kinds tables + README counts updated).

One full-suite run showed a flaky test_lsp_server code-action failure; it
passes 3/3 in isolation and the whole LSP file passes 3/3 — a pre-existing
subprocess-timing flake under load, unrelated to this change (the rule has no
fix_hcl; the code-action path is orthogonal).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added SEC-SECRETS-ENTROPY-001: A new security rule that detects probable hardcoded secrets using entropy analysis. Catches high-entropy token strings that name-based secret patterns miss.
  • Documentation

    • Updated rule catalog and documentation (353 → 354 total rules) to reflect the new entropy-based secret detection capability and its configuration parameters.

Review Change Stack

…high_entropy_string)

The audit's #1 capability quick-win — complements the name-based grep secret
rules (which only fire on password/secret/api_key-style fields) by catching
high-entropy tokens dropped into *any* argument, regardless of its name.

New pattern kind `high_entropy_string` (scripts/_handlers_security.py): for each
resource body (comments stripped first, length-preserving so lines stay
accurate), it scans `name = "literal"` assignments and flags values that are
charset-gated (base64/token), length-bounded (20–100), mix ≥2 character classes,
and clear a Shannon-entropy threshold (default 4.0 bits/char). Catalogue-
configurable: min_length / max_length / min_entropy.

Design calibrated empirically against real secrets vs. common Terraform
non-secrets:
- base64 charset + H≥4.0 separates genuine tokens (AWS secret 4.61, GitHub PAT
  4.14) from hex strings / git SHAs / image digests (only 16 symbols → H≈3.7) —
  so no special hex handling and the git-SHA false-positive class is avoided.
- excludes interpolations/refs (`${...}`, `var.*`), cloud resource-ids (ami-/
  vpc-/…), and single-character-class kebab/snake names (bucket names) that can
  clear the entropy bar by length alone.
- max_length drops long base64 blobs (certs / user_data) that aren't tokens.

Rule SEC-SECRETS-ENTROPY-001 (HIGH, CWE-798/CWE-259, T1552.001) is a heuristic —
findings ask the user to verify + rotate. No fix_hcl (can't auto-fix a secret).

Tests: tests/test_entropy_secrets.py (23 — entropy math, classifier exclusions,
handler) + dirty/clean fixtures via self_test (262/262 clean). Terragoat
unchanged (the rule adds zero findings there — conservative by design). Catalogue
353 → 354; rule docs regenerated; catalog/README + docs/custom-rules kinds tables
+ README counts updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented May 30, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR introduces SEC-SECRETS-ENTROPY-001, a new security detection rule that identifies probable hardcoded secrets in Terraform by analyzing string-literal entropy. The implementation includes a handler function computing Shannon entropy, comprehensive unit and integration tests, a complete rule catalog entry, test fixtures, and documentation.

Changes

Entropy-based secret detection

Layer / File(s) Summary
Shannon entropy and classification logic
scripts/_handlers_security.py, tests/test_entropy_secrets.py
Implements Shannon entropy calculation (_shannon_entropy), secret classification (_is_high_entropy_secret) with charset/length/entropy thresholds and exclusions for interpolations and resource ID prefixes; unit tests validate entropy computation and classifier behavior across token positives and false-positive exclusions.
Handler implementation and integration tests
scripts/_handlers_security.py, tests/test_entropy_secrets.py
Handler (_detect_high_entropy_string) scans Terraform resources for high-entropy string assignments, deduplicates findings by (arg, value), and emits structured detections; integration tests verify correct firing with metadata, silence on excluded values, and configurable threshold behavior.
Rule specification and test fixtures
catalog/SEC-SECRETS-ENTROPY-001.yaml, fixtures/sec_secrets_entropy_001/main.tf, fixtures/SEC-SECRETS-ENTROPY-001_clean/main.tf
YAML rule definition with metadata (HIGH severity, infrastructure-wide, MITRE T1552.001, CWEs 798/259), high-entropy pattern config (20–100 length, 4.0 min entropy), and guidance on remediation/verification/suppression; positive fixture with high-entropy token in session_key, negative fixture documenting exclusion cases.
Pattern documentation and rule page
catalog/README.md, docs/custom-rules.md, docs/rules/SEC-SECRETS-ENTROPY-001.md, docs/rules/index.md, README.md
Catalog and custom-rules pattern documentation for high_entropy_string including configurable parameters; comprehensive rule page with rationale, adversarial scenarios, verification steps, MITRE/CWE references, and operational guidance; index and README updates reflecting rule count increment (353 → 354).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A fuzzy detector hops with pride,
Shannon entropy as its guide,
High-entropy tokens cannot hide,
From secrets seekers far and wide! 🔐

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: adding entropy-based secret detection with a new pattern kind and rule.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/entropy-secret-detection

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/custom-rules.md`:
- Line 134: Update the documentation line for the `high_entropy_string` rule to
insert the missing word "as" so the phrase reads "marks it as a probable
hardcoded secret"; locate the table row containing `high_entropy_string` and
update the description accordingly.

In `@docs/rules/SEC-SECRETS-ENTROPY-001.md`:
- Line 40: Fix the grammar by hyphenating the compound adjective in the sentence
that reads "**High-entropy string literal — probable hardcoded secret (API token
/ access key).** This rule has `default_urgency: HIGH` and operates on a
infrastructure wide blast radius." — replace "infrastructure wide" with
"infrastructure-wide" so the phrase correctly reads "operates on an
infrastructure-wide blast radius."

In `@scripts/_handlers_security.py`:
- Around line 436-462: The handler currently only iterates c.resources, but the
rule text promises "any argument" — either expand the scan to include other
block kinds (e.g., iterate c.modules, c.data, c.variables, c.outputs,
c.providers or a generic c.blocks collection if available) and apply the same
logic using _ENTROPY_ASSIGN_RE, _is_high_entropy_secret and the out.append
payload, or else tighten the rule docs to say it only checks resource arguments;
update the loop that references c.resources and the comment/contract accordingly
so high-entropy literals in module/data/variable/output/provider blocks are
covered or the contract is explicit.

In `@tests/test_entropy_secrets.py`:
- Around line 23-24: The test uses a provider-shaped AWS secret fixture named
TOKEN (and additional provider-like fixtures in the same file) which can trigger
secret scanners; replace TOKEN and the other similar constants with neutral
high-entropy synthetic strings (random-looking but not matching AWS/GitHub
formats) that preserve the entropy characteristics the tests assert against,
update any assertions or expectations if they reference the literal value, and
ensure all other provider-shaped fixtures in that test file are similarly
replaced.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: dcecd10b-6ec7-4b54-b77a-0b37cdb228ea

📥 Commits

Reviewing files that changed from the base of the PR and between 05973f7 and 5653ec8.

📒 Files selected for processing (10)
  • README.md
  • catalog/README.md
  • catalog/SEC-SECRETS-ENTROPY-001.yaml
  • docs/custom-rules.md
  • docs/rules/SEC-SECRETS-ENTROPY-001.md
  • docs/rules/index.md
  • fixtures/SEC-SECRETS-ENTROPY-001_clean/main.tf
  • fixtures/sec_secrets_entropy_001/main.tf
  • scripts/_handlers_security.py
  • tests/test_entropy_secrets.py

Comment thread docs/custom-rules.md
| `hcl_attr` | A resource attribute equals (or doesn't equal) a specific value |
| `grep` | Plain-text regex search |
| `iam_policy_analysis` | Walk `data.aws_iam_policy_document` statements (see below) |
| `high_entropy_string` | A string literal whose Shannon entropy marks it a probable hardcoded secret (any arg name) — complements the name-based secret greps |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Grammar: Insert missing word "as".

The phrase "marks it a probable hardcoded secret" should be "marks it as a probable hardcoded secret".

📝 Proposed fix
-| `high_entropy_string` | A string literal whose Shannon entropy marks it a probable hardcoded secret (any arg name) — complements the name-based secret greps |
+| `high_entropy_string` | A string literal whose Shannon entropy marks it as a probable hardcoded secret (any arg name) — complements the name-based secret greps |
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
| `high_entropy_string` | A string literal whose Shannon entropy marks it a probable hardcoded secret (any arg name) — complements the name-based secret greps |
| `high_entropy_string` | A string literal whose Shannon entropy marks it as a probable hardcoded secret (any arg name) — complements the name-based secret greps |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/custom-rules.md` at line 134, Update the documentation line for the
`high_entropy_string` rule to insert the missing word "as" so the phrase reads
"marks it as a probable hardcoded secret"; locate the table row containing
`high_entropy_string` and update the description accordingly.


<p><a href="vscode://tfanalyze.tf-analyze/rule/SEC-SECRETS-ENTROPY-001" style="display:inline-block;padding:6px 12px;background:#157878;color:#fff;text-decoration:none;border-radius:4px;font-weight:600;font-size:14px;margin-top:6px">📂 Open in VS Code</a><a href="vscode://tfanalyze.tf-analyze/suppress?id=SEC-SECRETS-ENTROPY-001" style="display:inline-block;padding:6px 12px;background:#fff;color:#c27a00;text-decoration:none;border:1px solid #c27a00;border-radius:4px;font-weight:600;font-size:14px;margin-top:6px;margin-left:6px" title="Add SEC-SECRETS-ENTROPY-001 to .tf-analyze.yaml's ignore_rules in your workspace">📝 Suppress in workspace</a> <span style="color:#666;font-size:12px;margin-left:4px">(requires the <a href="https://marketplace.visualstudio.com/items?itemName=tfanalyze.tf-analyze" style="color:#157878">tf-analyze extension</a>)</span></p>

> **High-entropy string literal — probable hardcoded secret (API token / access key).** This rule has `default_urgency: HIGH` and operates on a infrastructure wide blast radius.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Grammar: Hyphenate compound adjective.

"infrastructure wide" should be "infrastructure-wide" when used as a compound adjective.

📝 Proposed fix
-> **High-entropy string literal — probable hardcoded secret (API token / access key).** This rule has `default_urgency: HIGH` and operates on a infrastructure wide blast radius. 
+> **High-entropy string literal — probable hardcoded secret (API token / access key).** This rule has `default_urgency: HIGH` and operates on an infrastructure-wide blast radius. 
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
> **High-entropy string literal — probable hardcoded secret (API token / access key).** This rule has `default_urgency: HIGH` and operates on a infrastructure wide blast radius.
> **High-entropy string literal — probable hardcoded secret (API token / access key).** This rule has `default_urgency: HIGH` and operates on an infrastructure-wide blast radius.
🧰 Tools
🪛 LanguageTool

[grammar] ~40-~40: Use a hyphen to join words.
Context: ...: HIGH` and operates on a infrastructure wide blast radius. ## What this checks...

(QB_NEW_EN_HYPHEN)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/rules/SEC-SECRETS-ENTROPY-001.md` at line 40, Fix the grammar by
hyphenating the compound adjective in the sentence that reads "**High-entropy
string literal — probable hardcoded secret (API token / access key).** This rule
has `default_urgency: HIGH` and operates on a infrastructure wide blast radius."
— replace "infrastructure wide" with "infrastructure-wide" so the phrase
correctly reads "operates on an infrastructure-wide blast radius."

Comment on lines +436 to +462
for blk in c.resources:
btype, bname = blk["groups"]
body = strip_hcl_context(blk["body"]) # blank comments, keep offsets
seen: set[tuple[str, str]] = set()
for m in _ENTROPY_ASSIGN_RE.finditer(body):
arg, value = m.group(1), m.group(2)
if (arg, value) in seen:
continue
if not _is_high_entropy_secret(
value, min_len=min_len, max_len=max_len, min_entropy=min_entropy
):
continue
seen.add((arg, value))
line = blk["start_line"] + body.count("\n", 0, m.start())
ent = _shannon_entropy(value)
out.append({
"id": c.eid,
"file": str(c.file_path),
"line": line,
"resource": f"{btype}.{bname}",
"context": (
f"high-entropy string assigned to `{arg}` "
f"(entropy {ent:.2f} bits/char, length {len(value)}) "
f"— probable hardcoded secret"
),
})
return out

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Broaden this beyond resource blocks or narrow the rule contract.

The rule text says this catches high-entropy literals in any argument, but the handler only walks c.resources. That misses the same hardcoded secret in module, data, variable default, output, and provider blocks, so the implementation under-delivers on the advertised coverage.

At minimum, either scan the additional block kinds from c.text as well, or explicitly scope the catalogue/docs to “resource arguments only.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/_handlers_security.py` around lines 436 - 462, The handler currently
only iterates c.resources, but the rule text promises "any argument" — either
expand the scan to include other block kinds (e.g., iterate c.modules, c.data,
c.variables, c.outputs, c.providers or a generic c.blocks collection if
available) and apply the same logic using _ENTROPY_ASSIGN_RE,
_is_high_entropy_secret and the out.append payload, or else tighten the rule
docs to say it only checks resource arguments; update the loop that references
c.resources and the comment/contract accordingly so high-entropy literals in
module/data/variable/output/provider blocks are covered or the contract is
explicit.

Comment on lines +23 to +24
# A well-known *example* AWS secret-key string (not a live credential).
TOKEN = "wJalrXUtnFEMIK7MDENGbPxRfiCYEXAMPLEKEY"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use neutral high-entropy fixtures instead of provider-shaped credentials.

These literals are realistic enough to trip secret scanners already, which will create noisy CI and false incident churn even though they are test data. Since the behavior under test is entropy-based, you can keep the same coverage with synthetic tokens that do not match AWS/GitHub credential formats.

Suggested test-data swap
-# A well-known *example* AWS secret-key string (not a live credential).
-TOKEN = "wJalrXUtnFEMIK7MDENGbPxRfiCYEXAMPLEKEY"
+# Neutral high-entropy fixture; shaped for entropy tests, not vendor scanners.
+TOKEN = "kT9xQ2mZ7pL4vR8nW1cF3hJ6dS5gB0yA7zXqWeRt"
@@
     `@pytest.mark.parametrize`("v", [
         TOKEN,                                        # AWS secret-style, H=4.61
-        "ghp_16C7e42F292c6912E7710c838347Ae178B4a",   # GitHub PAT-ish, H=4.14
-        "kT9xQ2mZ7pL4vR8nW1cF3hJ6dS5gB0yA7zXqWeRt",   # random base64 token
+        "Q7mR2xLp8Vn4cF1hJ6dS5gB0yA7zXqWeRt3NkTuPw",   # neutral token
+        "M4vR8nW1cF3hJ6dS5gB0yA7zXqWeRt9PkLs2DfGh",   # neutral token
     ])

Also applies to: 51-55

🧰 Tools
🪛 Ruff (0.15.14)

[error] 24-24: Possible hardcoded password assigned to: "TOKEN"

(S105)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_entropy_secrets.py` around lines 23 - 24, The test uses a
provider-shaped AWS secret fixture named TOKEN (and additional provider-like
fixtures in the same file) which can trigger secret scanners; replace TOKEN and
the other similar constants with neutral high-entropy synthetic strings
(random-looking but not matching AWS/GitHub formats) that preserve the entropy
characteristics the tests assert against, update any assertions or expectations
if they reference the literal value, and ensure all other provider-shaped
fixtures in that test file are similarly replaced.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant