proofctl 0.2.0__tar.gz → 0.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {proofctl-0.2.0 → proofctl-0.2.1}/PKG-INFO +1 -1
- {proofctl-0.2.0 → proofctl-0.2.1}/proofctl/checkers/terraform.py +8 -1
- {proofctl-0.2.0 → proofctl-0.2.1}/proofctl/config.py +59 -3
- {proofctl-0.2.0 → proofctl-0.2.1}/proofctl/engine.py +18 -1
- {proofctl-0.2.0 → proofctl-0.2.1}/proofctl.egg-info/PKG-INFO +1 -1
- {proofctl-0.2.0 → proofctl-0.2.1}/pyproject.toml +1 -1
- {proofctl-0.2.0 → proofctl-0.2.1}/tests/test_config_proofctl.py +31 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/tests/test_engine.py +68 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/tests/test_terraform_ext.py +30 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/LICENSE +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/README.md +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/proofctl/__init__.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/proofctl/_globmatch.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/proofctl/baseline.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/proofctl/checkers/__init__.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/proofctl/checkers/base.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/proofctl/checkers/dockerfile.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/proofctl/checkers/hcl_utils.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/proofctl/checkers/imports.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/proofctl/checkers/leakage.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/proofctl/checkers/llm_integration.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/proofctl/checkers/placeholders.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/proofctl/checkers/quality.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/proofctl/checkers/security.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/proofctl/checkers/shell_checker.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/proofctl/checkers/terragrunt.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/proofctl/checkers/variants.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/proofctl/checkers/yaml_checker.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/proofctl/cli.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/proofctl/fixer.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/proofctl/models.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/proofctl/registry.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/proofctl/reporters/__init__.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/proofctl/reporters/html_reporter.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/proofctl/reporters/json_reporter.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/proofctl/reporters/terminal.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/proofctl.egg-info/SOURCES.txt +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/proofctl.egg-info/dependency_links.txt +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/proofctl.egg-info/entry_points.txt +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/proofctl.egg-info/requires.txt +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/proofctl.egg-info/top_level.txt +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/setup.cfg +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/tests/test_baseline.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/tests/test_dockerfile.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/tests/test_fixer.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/tests/test_imports.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/tests/test_leakage.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/tests/test_llm.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/tests/test_local_packages.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/tests/test_phase1_audit_fixes.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/tests/test_phase2_audit_fixes.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/tests/test_phase3_audit_fixes.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/tests/test_phase4_audit_fixes.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/tests/test_phase5_llm_imports_fixes.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/tests/test_phase5_p_l_v_fixes.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/tests/test_phase5_quality_fixes.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/tests/test_phase6_audit_fixes.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/tests/test_phase8_audit_fixes.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/tests/test_placeholders.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/tests/test_quality.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/tests/test_quality_ext.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/tests/test_security.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/tests/test_security_ext.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/tests/test_suppression.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/tests/test_terraform.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/tests/test_terragrunt.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/tests/test_variants.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/tests/test_yaml.py +0 -0
- {proofctl-0.2.0 → proofctl-0.2.1}/tests/test_yaml_k8s_gha.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: proofctl
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Zero-dependency linter for Python, Terraform, Dockerfiles, Kubernetes, and GitHub Actions — catches AI slop and security misconfigurations pre-commit
|
|
5
5
|
Author-email: Kolawolu Odunola <kolawolu.o@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -1997,8 +1997,15 @@ class TerraformChecker(HclFileChecker):
|
|
|
1997
1997
|
|
|
1998
1998
|
def _t015(self, path: Path, blocks: list[HclBlock]) -> list[Finding]:
|
|
1999
1999
|
"""T-015 — Output with sensitive-looking name but sensitive = true not set."""
|
|
2000
|
+
# "key" is excluded when followed by measurement/metadata suffixes
|
|
2001
|
+
# (e.g. max_key_age_days, key_count, key_rotation_period) — those are
|
|
2002
|
+
# not sensitive values, just numeric descriptors that happen to contain
|
|
2003
|
+
# the word "key". Other terms (password, secret, token, credential)
|
|
2004
|
+
# are always sensitive regardless of suffix.
|
|
2000
2005
|
_sensitive_name_pat = re.compile(
|
|
2001
|
-
r'(?:password|secret|token|
|
|
2006
|
+
r'(?:password|secret|token|credential'
|
|
2007
|
+
r'|key(?!_age|_days|_hours|_seconds|_count|_max|_min|_num'
|
|
2008
|
+
r'|_threshold|_expir|_rotation|_version|_size|_length))',
|
|
2002
2009
|
re.IGNORECASE,
|
|
2003
2010
|
)
|
|
2004
2011
|
out = []
|
|
@@ -9,16 +9,72 @@ import yaml
|
|
|
9
9
|
from .models import Severity
|
|
10
10
|
|
|
11
11
|
_DEFAULT_EXCLUDES: list[str] = [
|
|
12
|
+
# ── Python project environments ───────────────────────────────────────────
|
|
12
13
|
"**/.venv/**",
|
|
13
14
|
"**/venv/**",
|
|
14
|
-
"**/node_modules/**",
|
|
15
|
-
"**/.git/**",
|
|
16
15
|
"**/__pycache__/**",
|
|
17
16
|
"**/*.egg-info/**",
|
|
18
17
|
"**/dist/**",
|
|
19
18
|
"**/build/**",
|
|
20
|
-
|
|
19
|
+
# ── Universal VCS / package managers ─────────────────────────────────────
|
|
20
|
+
"**/.git/**",
|
|
21
|
+
"**/node_modules/**",
|
|
22
|
+
# ── Terraform / Terragrunt caches ─────────────────────────────────────────
|
|
21
23
|
"**/.terraform/**",
|
|
24
|
+
"**/.terragrunt-cache/**",
|
|
25
|
+
# Terraform/OpenTofu state and plan artifacts — never hand-authored;
|
|
26
|
+
# may contain sensitive resource outputs and secrets.
|
|
27
|
+
"**/*.tfstate",
|
|
28
|
+
"**/*.tfstate.backup",
|
|
29
|
+
"**/*.tfplan",
|
|
30
|
+
# ── Flux CD ───────────────────────────────────────────────────────────────
|
|
31
|
+
# Both files are auto-generated by `flux bootstrap` and contain the comment
|
|
32
|
+
# "# This manifest was generated by flux. DO NOT EDIT."
|
|
33
|
+
# gotk-components.yaml carries wildcard RBAC + cluster-admin bindings that
|
|
34
|
+
# are intentional Flux design; gotk-sync.yaml wires the GitRepository/
|
|
35
|
+
# Kustomization that reconciles flux-system itself. Neither is user policy.
|
|
36
|
+
"**/flux-system/gotk-components.yaml",
|
|
37
|
+
"**/flux-system/gotk-sync.yaml",
|
|
38
|
+
# ── ArgoCD Autopilot ──────────────────────────────────────────────────────
|
|
39
|
+
# Files generated by `argocd-autopilot repo bootstrap`. The kustomization
|
|
40
|
+
# references upstream install.yaml which carries a cluster-admin binding for
|
|
41
|
+
# argocd-application-controller — intentional, not a misconfiguration.
|
|
42
|
+
"**/bootstrap/argo-cd/kustomization.yaml",
|
|
43
|
+
"**/bootstrap/argo-cd.yaml",
|
|
44
|
+
"**/bootstrap/cluster-resources/in-cluster/**",
|
|
45
|
+
# ── AWS CDK / CDK for Terraform ───────────────────────────────────────────
|
|
46
|
+
# cdk.out/ is synthesized CloudFormation JSON from `cdk synth`.
|
|
47
|
+
# .gen/ and cdktf.out/ are CDKTF provider bindings and synth output.
|
|
48
|
+
"**/cdk.out/**",
|
|
49
|
+
"**/.gen/**",
|
|
50
|
+
"**/cdktf.out/**",
|
|
51
|
+
# ── Helm ─────────────────────────────────────────────────────────────────
|
|
52
|
+
# Chart tarballs downloaded by `helm dep update` into charts/ subdirectories.
|
|
53
|
+
"**/charts/*.tgz",
|
|
54
|
+
# ── Pulumi ───────────────────────────────────────────────────────────────
|
|
55
|
+
# Local backend state written by `pulumi up` when using file:// backend.
|
|
56
|
+
"**/.pulumi/**",
|
|
57
|
+
# ── Other IaC tool caches ─────────────────────────────────────────────────
|
|
58
|
+
"**/packer_cache/**", # Packer: ISO/VM template cache (default ./packer_cache/)
|
|
59
|
+
"**/.vagrant/**", # Vagrant: machine state, private keys, VM references
|
|
60
|
+
"**/.crossplane/**", # Crossplane CLI: provider OCI layer / CRD schema cache
|
|
61
|
+
# ── Ansible Galaxy ────────────────────────────────────────────────────────
|
|
62
|
+
# Collections installed via `ansible-galaxy collection install`.
|
|
63
|
+
"**/collections/ansible_collections/**",
|
|
64
|
+
# ── Go vendor ─────────────────────────────────────────────────────────────
|
|
65
|
+
# Third-party module source vendored by `go mod vendor`. Contains .go files
|
|
66
|
+
# that can trigger false-positives in import / quality rules.
|
|
67
|
+
"**/vendor/**",
|
|
68
|
+
# ── Python tool caches ────────────────────────────────────────────────────
|
|
69
|
+
# These directories contain .py files and can surface false-positives if
|
|
70
|
+
# scanned (e.g. generated test helpers, mypy stubs, hypothesis examples).
|
|
71
|
+
"**/.tox/**",
|
|
72
|
+
"**/.nox/**",
|
|
73
|
+
"**/.pytest_cache/**",
|
|
74
|
+
"**/.mypy_cache/**",
|
|
75
|
+
"**/.ruff_cache/**",
|
|
76
|
+
"**/.hypothesis/**",
|
|
77
|
+
"**/htmlcov/**",
|
|
22
78
|
]
|
|
23
79
|
|
|
24
80
|
# Top-level config keys recognised by `.proofctl.yaml`. Anything else triggers
|
|
@@ -411,7 +411,24 @@ def run_check(
|
|
|
411
411
|
floor = Severity.from_str(min_severity)
|
|
412
412
|
findings = [f for f in findings if f.severity >= floor]
|
|
413
413
|
|
|
414
|
-
|
|
414
|
+
sorted_findings = sorted(findings, key=lambda x: (-x.severity, x.file, x.line or 0))
|
|
415
|
+
|
|
416
|
+
# ── 8. Module-level rule deduplication. ───────────────────────────────────
|
|
417
|
+
# Rules like T014 (missing required_version) represent a per-module property,
|
|
418
|
+
# not a per-file one. A module with 10 .tf files should produce one finding,
|
|
419
|
+
# not ten. After sorting we keep the first occurrence per (dir, rule_id) so
|
|
420
|
+
# the preserved finding is always the alphabetically-first file in the module.
|
|
421
|
+
_MODULE_LEVEL_RULES: frozenset[str] = frozenset({"PROOFCTL-TF-T014"})
|
|
422
|
+
seen_module_dirs: set[tuple[str, str]] = set()
|
|
423
|
+
result: list[Finding] = []
|
|
424
|
+
for f in sorted_findings:
|
|
425
|
+
if f.rule_id in _MODULE_LEVEL_RULES:
|
|
426
|
+
dir_key = (str(Path(f.file).parent), f.rule_id)
|
|
427
|
+
if dir_key in seen_module_dirs:
|
|
428
|
+
continue
|
|
429
|
+
seen_module_dirs.add(dir_key)
|
|
430
|
+
result.append(f)
|
|
431
|
+
return result
|
|
415
432
|
|
|
416
433
|
|
|
417
434
|
def _normalise(f: Finding, root: Path, config: ProofctlConfig) -> Finding | None:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: proofctl
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Zero-dependency linter for Python, Terraform, Dockerfiles, Kubernetes, and GitHub Actions — catches AI slop and security misconfigurations pre-commit
|
|
5
5
|
Author-email: Kolawolu Odunola <kolawolu.o@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "proofctl"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.1"
|
|
8
8
|
description = "Zero-dependency linter for Python, Terraform, Dockerfiles, Kubernetes, and GitHub Actions — catches AI slop and security misconfigurations pre-commit"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -20,6 +20,37 @@ def test_load_config_no_file_returns_defaults(tmp_path):
|
|
|
20
20
|
assert cfg.any_threshold == 3
|
|
21
21
|
|
|
22
22
|
|
|
23
|
+
def test_default_excludes_include_vendor_generated_paths(tmp_path):
|
|
24
|
+
"""All vendor/generated tool paths must be present in default excludes."""
|
|
25
|
+
cfg = load_config(tmp_path)
|
|
26
|
+
required = [
|
|
27
|
+
# Flux CD bootstrap files
|
|
28
|
+
"**/flux-system/gotk-components.yaml",
|
|
29
|
+
"**/flux-system/gotk-sync.yaml",
|
|
30
|
+
# ArgoCD Autopilot bootstrap files
|
|
31
|
+
"**/bootstrap/argo-cd/kustomization.yaml",
|
|
32
|
+
"**/bootstrap/argo-cd.yaml",
|
|
33
|
+
# Terraform generated artifacts
|
|
34
|
+
"**/*.tfstate",
|
|
35
|
+
"**/*.tfplan",
|
|
36
|
+
# CDK tools
|
|
37
|
+
"**/cdk.out/**",
|
|
38
|
+
"**/.gen/**",
|
|
39
|
+
# Helm chart tarballs
|
|
40
|
+
"**/charts/*.tgz",
|
|
41
|
+
# Pulumi local backend
|
|
42
|
+
"**/.pulumi/**",
|
|
43
|
+
# Go vendor
|
|
44
|
+
"**/vendor/**",
|
|
45
|
+
# Python caches
|
|
46
|
+
"**/.tox/**",
|
|
47
|
+
"**/.pytest_cache/**",
|
|
48
|
+
"**/.mypy_cache/**",
|
|
49
|
+
]
|
|
50
|
+
for pat in required:
|
|
51
|
+
assert pat in cfg.exclude_paths, f"missing default exclude: {pat}"
|
|
52
|
+
|
|
53
|
+
|
|
23
54
|
def test_load_config_empty_file_returns_defaults(tmp_path):
|
|
24
55
|
(tmp_path / ".proofctl.yaml").write_text("")
|
|
25
56
|
cfg = load_config(tmp_path)
|
|
@@ -48,6 +48,57 @@ def test_engine_excludes_configured_paths(tmp_path):
|
|
|
48
48
|
assert all("venv" not in f.file for f in findings)
|
|
49
49
|
|
|
50
50
|
|
|
51
|
+
_WILDCARD_RBAC_YAML = (
|
|
52
|
+
"apiVersion: rbac.authorization.k8s.io/v1\n"
|
|
53
|
+
"kind: ClusterRole\n"
|
|
54
|
+
"metadata:\n"
|
|
55
|
+
" name: crd-controller-flux-system\n"
|
|
56
|
+
"rules:\n"
|
|
57
|
+
"- apiGroups: ['*']\n"
|
|
58
|
+
" resources: ['*']\n"
|
|
59
|
+
" verbs: ['*']\n"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_engine_excludes_flux_gotk_components(tmp_path):
|
|
64
|
+
"""Flux bootstrap gotk-components.yaml must be skipped by default (vendor file)."""
|
|
65
|
+
flux_dir = tmp_path / "clusters" / "prod" / "flux-system"
|
|
66
|
+
flux_dir.mkdir(parents=True)
|
|
67
|
+
(flux_dir / "gotk-components.yaml").write_text(_WILDCARD_RBAC_YAML)
|
|
68
|
+
findings = run_check(tmp_path, _cfg(), families=["YAML"])
|
|
69
|
+
assert not any("gotk-components" in f.file for f in findings)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def test_engine_excludes_flux_gotk_sync(tmp_path):
|
|
73
|
+
"""Flux bootstrap gotk-sync.yaml must be skipped by default."""
|
|
74
|
+
flux_dir = tmp_path / "flux-system"
|
|
75
|
+
flux_dir.mkdir(parents=True)
|
|
76
|
+
(flux_dir / "gotk-sync.yaml").write_text(
|
|
77
|
+
"apiVersion: source.toolkit.fluxcd.io/v1\n"
|
|
78
|
+
"kind: GitRepository\n"
|
|
79
|
+
"metadata:\n"
|
|
80
|
+
" name: flux-system\n"
|
|
81
|
+
" namespace: flux-system\n"
|
|
82
|
+
)
|
|
83
|
+
findings = run_check(tmp_path, _cfg(), families=["YAML"])
|
|
84
|
+
assert not any("gotk-sync" in f.file for f in findings)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def test_engine_excludes_argocd_autopilot_bootstrap(tmp_path):
|
|
88
|
+
"""ArgoCD Autopilot bootstrap files must be skipped by default (vendor RBAC)."""
|
|
89
|
+
bootstrap_dir = tmp_path / "bootstrap" / "argo-cd"
|
|
90
|
+
bootstrap_dir.mkdir(parents=True)
|
|
91
|
+
(bootstrap_dir / "kustomization.yaml").write_text(_WILDCARD_RBAC_YAML)
|
|
92
|
+
(tmp_path / "bootstrap" / "argo-cd.yaml").write_text(
|
|
93
|
+
"apiVersion: argoproj.io/v1alpha1\nkind: Application\n"
|
|
94
|
+
"metadata:\n name: argo-cd\n namespace: argocd\n"
|
|
95
|
+
)
|
|
96
|
+
findings = run_check(tmp_path, _cfg(), families=["YAML"])
|
|
97
|
+
assert not any(
|
|
98
|
+
"bootstrap/argo-cd" in f.file.replace("\\", "/") for f in findings
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
51
102
|
def test_engine_respects_disabled_rules(tmp_path):
|
|
52
103
|
"""Rules in config.disable must produce zero findings."""
|
|
53
104
|
(tmp_path / "code.py").write_text(
|
|
@@ -86,3 +137,20 @@ def test_engine_deduplicates_findings(tmp_path):
|
|
|
86
137
|
findings = run_check(tmp_path, _cfg(), families=["S"])
|
|
87
138
|
keys = [f.dedup_key() for f in findings]
|
|
88
139
|
assert len(keys) == len(set(keys))
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def test_engine_t014_deduplicates_to_one_per_module_directory(tmp_path):
|
|
143
|
+
"""T014 must fire once per module directory, not once per .tf file."""
|
|
144
|
+
module = tmp_path / "atlantis"
|
|
145
|
+
module.mkdir()
|
|
146
|
+
# Three .tf files with resources but no required_version in any of them.
|
|
147
|
+
# HCL parser requires multi-line block syntax.
|
|
148
|
+
resource_tmpl = 'resource "google_storage_bucket" "b{n}" {{\n name = "b{n}"\n}}\n'
|
|
149
|
+
for name in ("apigateway.tf", "asg.tf", "cloudfunction.tf"):
|
|
150
|
+
(module / name).write_text(resource_tmpl.format(n=name[0]))
|
|
151
|
+
findings = run_check(tmp_path, _cfg(), families=["TF"])
|
|
152
|
+
t014 = [f for f in findings if f.rule_id == "PROOFCTL-TF-T014"]
|
|
153
|
+
assert len(t014) == 1, (
|
|
154
|
+
f"Expected 1 T014 finding for the module, got {len(t014)}: "
|
|
155
|
+
+ ", ".join(f.file for f in t014)
|
|
156
|
+
)
|
|
@@ -1119,3 +1119,33 @@ output "encryption_key" {
|
|
|
1119
1119
|
"""
|
|
1120
1120
|
f = _find(_checker.check(_P, src), "PROOFCTL-TF-T015")
|
|
1121
1121
|
assert len(f) == 1
|
|
1122
|
+
|
|
1123
|
+
|
|
1124
|
+
def test_t015_key_age_days_is_not_sensitive():
|
|
1125
|
+
"""max_key_age_days is a numeric duration, not a sensitive value (FP regression)."""
|
|
1126
|
+
src = """
|
|
1127
|
+
output "max_key_age_days" {
|
|
1128
|
+
value = 90
|
|
1129
|
+
}
|
|
1130
|
+
"""
|
|
1131
|
+
assert "PROOFCTL-TF-T015" not in _ids(_checker.check(_P, src))
|
|
1132
|
+
|
|
1133
|
+
|
|
1134
|
+
def test_t015_key_count_is_not_sensitive():
|
|
1135
|
+
src = """
|
|
1136
|
+
output "key_rotation_days" {
|
|
1137
|
+
value = 30
|
|
1138
|
+
}
|
|
1139
|
+
"""
|
|
1140
|
+
assert "PROOFCTL-TF-T015" not in _ids(_checker.check(_P, src))
|
|
1141
|
+
|
|
1142
|
+
|
|
1143
|
+
def test_t015_key_name_still_fires():
|
|
1144
|
+
"""Output 'key_name' references a key resource — should still be flagged."""
|
|
1145
|
+
src = """
|
|
1146
|
+
output "kms_key_name" {
|
|
1147
|
+
value = google_kms_crypto_key.main.name
|
|
1148
|
+
}
|
|
1149
|
+
"""
|
|
1150
|
+
f = _find(_checker.check(_P, src), "PROOFCTL-TF-T015")
|
|
1151
|
+
assert len(f) == 1
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|