proofctl 0.5.5__tar.gz → 0.5.7__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.5.5/proofctl.egg-info → proofctl-0.5.7}/PKG-INFO +3 -3
- {proofctl-0.5.5 → proofctl-0.5.7}/README.md +2 -2
- {proofctl-0.5.5 → proofctl-0.5.7}/proofctl/checkers/imports.py +0 -108
- {proofctl-0.5.5 → proofctl-0.5.7}/proofctl/checkers/terraform.py +66 -3
- {proofctl-0.5.5 → proofctl-0.5.7}/proofctl/engine.py +1 -3
- {proofctl-0.5.5 → proofctl-0.5.7}/proofctl/registry.py +0 -1
- {proofctl-0.5.5 → proofctl-0.5.7/proofctl.egg-info}/PKG-INFO +3 -3
- {proofctl-0.5.5 → proofctl-0.5.7}/pyproject.toml +1 -1
- {proofctl-0.5.5 → proofctl-0.5.7}/LICENSE +0 -0
- {proofctl-0.5.5 → proofctl-0.5.7}/MANIFEST.in +0 -0
- {proofctl-0.5.5 → proofctl-0.5.7}/proofctl/__init__.py +0 -0
- {proofctl-0.5.5 → proofctl-0.5.7}/proofctl/_globmatch.py +0 -0
- {proofctl-0.5.5 → proofctl-0.5.7}/proofctl/baseline.py +0 -0
- {proofctl-0.5.5 → proofctl-0.5.7}/proofctl/checkers/__init__.py +0 -0
- {proofctl-0.5.5 → proofctl-0.5.7}/proofctl/checkers/base.py +0 -0
- {proofctl-0.5.5 → proofctl-0.5.7}/proofctl/checkers/dockerfile.py +0 -0
- {proofctl-0.5.5 → proofctl-0.5.7}/proofctl/checkers/hcl_utils.py +0 -0
- {proofctl-0.5.5 → proofctl-0.5.7}/proofctl/checkers/leakage.py +0 -0
- {proofctl-0.5.5 → proofctl-0.5.7}/proofctl/checkers/llm_integration.py +0 -0
- {proofctl-0.5.5 → proofctl-0.5.7}/proofctl/checkers/placeholders.py +0 -0
- {proofctl-0.5.5 → proofctl-0.5.7}/proofctl/checkers/quality.py +0 -0
- {proofctl-0.5.5 → proofctl-0.5.7}/proofctl/checkers/secrets.py +0 -0
- {proofctl-0.5.5 → proofctl-0.5.7}/proofctl/checkers/security.py +0 -0
- {proofctl-0.5.5 → proofctl-0.5.7}/proofctl/checkers/shell_checker.py +0 -0
- {proofctl-0.5.5 → proofctl-0.5.7}/proofctl/checkers/terragrunt.py +0 -0
- {proofctl-0.5.5 → proofctl-0.5.7}/proofctl/checkers/variants.py +0 -0
- {proofctl-0.5.5 → proofctl-0.5.7}/proofctl/checkers/yaml_checker.py +0 -0
- {proofctl-0.5.5 → proofctl-0.5.7}/proofctl/cli.py +0 -0
- {proofctl-0.5.5 → proofctl-0.5.7}/proofctl/config.py +0 -0
- {proofctl-0.5.5 → proofctl-0.5.7}/proofctl/fixer.py +0 -0
- {proofctl-0.5.5 → proofctl-0.5.7}/proofctl/models.py +0 -0
- {proofctl-0.5.5 → proofctl-0.5.7}/proofctl/reporters/__init__.py +0 -0
- {proofctl-0.5.5 → proofctl-0.5.7}/proofctl/reporters/html_reporter.py +0 -0
- {proofctl-0.5.5 → proofctl-0.5.7}/proofctl/reporters/json_reporter.py +0 -0
- {proofctl-0.5.5 → proofctl-0.5.7}/proofctl/reporters/terminal.py +0 -0
- {proofctl-0.5.5 → proofctl-0.5.7}/proofctl.egg-info/SOURCES.txt +0 -0
- {proofctl-0.5.5 → proofctl-0.5.7}/proofctl.egg-info/dependency_links.txt +0 -0
- {proofctl-0.5.5 → proofctl-0.5.7}/proofctl.egg-info/entry_points.txt +0 -0
- {proofctl-0.5.5 → proofctl-0.5.7}/proofctl.egg-info/requires.txt +0 -0
- {proofctl-0.5.5 → proofctl-0.5.7}/proofctl.egg-info/top_level.txt +0 -0
- {proofctl-0.5.5 → proofctl-0.5.7}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: proofctl
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.7
|
|
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
|
|
@@ -28,7 +28,7 @@ Dynamic: license-file
|
|
|
28
28
|
|
|
29
29
|
# proofctl
|
|
30
30
|
|
|
31
|
-
A linter built for the DevOps stack — Python, Terraform, Kubernetes, Helm, Dockerfiles, GitHub Actions, Shell — that catches the specific kinds of mistakes AI coding tools introduce. **
|
|
31
|
+
A linter built for the DevOps stack — Python, Terraform, Kubernetes, Helm, Dockerfiles, GitHub Actions, Shell — that catches the specific kinds of mistakes AI coding tools introduce. **268 rules**, zero dependencies, runs in under a second.
|
|
32
32
|
|
|
33
33
|
```
|
|
34
34
|
proofctl check .
|
|
@@ -172,7 +172,7 @@ git commit -m "chore: add proofctl baseline"
|
|
|
172
172
|
| Python — Quality (`Q`) | 17 | Mutable defaults, broad `except`, complexity, long functions, AI-slop patterns (echo docstrings, single-method classes, stale kwargs, defensive guards) |
|
|
173
173
|
| Python — Security (`S`) | 24 | OWASP Top-10 + CWE coverage: SQLi, command injection, weak crypto, JWT bypass, path traversal, XSS, SSRF, file upload, CSRF, missing authz, XPath injection, open redirect, TLS bypass |
|
|
174
174
|
| Secrets (`SECRET`) | 13 | AWS / GCP / GitHub / Slack / Stripe / private keys / JWTs / hardcoded passwords / K8s Secret YAML / connection strings with embedded creds |
|
|
175
|
-
| Python — Other |
|
|
175
|
+
| Python — Other | 11 | Placeholders, leakage (cross-language idioms, PII), import hallucination, variant detection, LLM guardrails (OWASP LLM Top-10) |
|
|
176
176
|
| Terraform — General + AI (`TF-T`, `TF-AI`) | 29 | required_version, sensitive outputs, IAM wildcards, mutable git refs, deprecated resources, AWS-region-in-GCP, placeholder values, image-family pinning, sensitive vars in VM metadata, unused variable declarations |
|
|
177
177
|
| Terraform — AWS (`TF-A`) | 40 | EC2 IMDSv2, S3 public-access-block, EBS encryption, VPC flow logs, KMS rotation, RDS hardening, SG / NACL / EKS / DynamoDB / ElastiCache / CloudTrail / IAM |
|
|
178
178
|
| Terraform — GCP (`TF-G`) | 52 | GKE hardening, GCS public-access prevention, IAM roles, KMS rotation, Cloud Function/Run public, BigQuery, Pub/Sub CMEK, Cloud SQL (TLS ssl_mode aware), firewall ingress, compute access scopes, Secret Manager CMEK consistency |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# proofctl
|
|
2
2
|
|
|
3
|
-
A linter built for the DevOps stack — Python, Terraform, Kubernetes, Helm, Dockerfiles, GitHub Actions, Shell — that catches the specific kinds of mistakes AI coding tools introduce. **
|
|
3
|
+
A linter built for the DevOps stack — Python, Terraform, Kubernetes, Helm, Dockerfiles, GitHub Actions, Shell — that catches the specific kinds of mistakes AI coding tools introduce. **268 rules**, zero dependencies, runs in under a second.
|
|
4
4
|
|
|
5
5
|
```
|
|
6
6
|
proofctl check .
|
|
@@ -144,7 +144,7 @@ git commit -m "chore: add proofctl baseline"
|
|
|
144
144
|
| Python — Quality (`Q`) | 17 | Mutable defaults, broad `except`, complexity, long functions, AI-slop patterns (echo docstrings, single-method classes, stale kwargs, defensive guards) |
|
|
145
145
|
| Python — Security (`S`) | 24 | OWASP Top-10 + CWE coverage: SQLi, command injection, weak crypto, JWT bypass, path traversal, XSS, SSRF, file upload, CSRF, missing authz, XPath injection, open redirect, TLS bypass |
|
|
146
146
|
| Secrets (`SECRET`) | 13 | AWS / GCP / GitHub / Slack / Stripe / private keys / JWTs / hardcoded passwords / K8s Secret YAML / connection strings with embedded creds |
|
|
147
|
-
| Python — Other |
|
|
147
|
+
| Python — Other | 11 | Placeholders, leakage (cross-language idioms, PII), import hallucination, variant detection, LLM guardrails (OWASP LLM Top-10) |
|
|
148
148
|
| Terraform — General + AI (`TF-T`, `TF-AI`) | 29 | required_version, sensitive outputs, IAM wildcards, mutable git refs, deprecated resources, AWS-region-in-GCP, placeholder values, image-family pinning, sensitive vars in VM metadata, unused variable declarations |
|
|
149
149
|
| Terraform — AWS (`TF-A`) | 40 | EC2 IMDSv2, S3 public-access-block, EBS encryption, VPC flow logs, KMS rotation, RDS hardening, SG / NACL / EKS / DynamoDB / ElastiCache / CloudTrail / IAM |
|
|
150
150
|
| Terraform — GCP (`TF-G`) | 52 | GKE hardening, GCS public-access prevention, IAM roles, KMS rotation, Cloud Function/Run public, BigQuery, Pub/Sub CMEK, Cloud SQL (TLS ssl_mode aware), firewall ingress, compute access scopes, Secret Manager CMEK consistency |
|
|
@@ -294,13 +294,6 @@ def _parse_setup_cfg(text: str) -> list[tuple[str, str | None, int]]:
|
|
|
294
294
|
results.append((m.group(1), m.group(3) or None, lineno))
|
|
295
295
|
return results
|
|
296
296
|
|
|
297
|
-
# mypy error patterns indicating phantom method calls
|
|
298
|
-
_MYPY_ATTR_RE = re.compile(
|
|
299
|
-
r'^(.+):(\d+):\s+error:\s+(?:Item\s+"[^"]+"\s+of\s+)?"([^"]+)"\s+has\s+no\s+attribute\s+"([^"]+)"'
|
|
300
|
-
r'|^(.+):(\d+):\s+error:\s+Module\s+"[^"]+"\s+has\s+no\s+attribute\s+"([^"]+)"'
|
|
301
|
-
)
|
|
302
|
-
|
|
303
|
-
|
|
304
297
|
def _normalize_pkg_name(name: str) -> str:
|
|
305
298
|
return re.sub(r"[-_.]+", "-", name).lower()
|
|
306
299
|
|
|
@@ -817,104 +810,3 @@ class DependencyChecker(DirectoryChecker):
|
|
|
817
810
|
return findings
|
|
818
811
|
|
|
819
812
|
|
|
820
|
-
class MethodChecker(DirectoryChecker):
|
|
821
|
-
"""PROOFCTL-M-001: Phantom method calls detected by a single mypy invocation.
|
|
822
|
-
|
|
823
|
-
Running mypy once on all files is orders of magnitude faster than once per
|
|
824
|
-
file: mypy's startup and import-resolution cost is paid once, not N times.
|
|
825
|
-
"""
|
|
826
|
-
|
|
827
|
-
def check(self, root: Path, py_files: list[Path]) -> list[Finding]:
|
|
828
|
-
if not py_files:
|
|
829
|
-
return []
|
|
830
|
-
if not shutil.which("mypy"):
|
|
831
|
-
# Surface the disabled state as a single INFO finding so CI users
|
|
832
|
-
# don't silently lose phantom-method coverage.
|
|
833
|
-
return [self._mypy_missing_finding(root)]
|
|
834
|
-
try:
|
|
835
|
-
return self._run_mypy(py_files)
|
|
836
|
-
except FileNotFoundError:
|
|
837
|
-
return [self._mypy_missing_finding(root)]
|
|
838
|
-
|
|
839
|
-
def _mypy_missing_finding(self, root: Path) -> Finding:
|
|
840
|
-
return Finding(
|
|
841
|
-
file=str(root),
|
|
842
|
-
line=None,
|
|
843
|
-
col=None,
|
|
844
|
-
rule_id="PROOFCTL-M-001",
|
|
845
|
-
rule_name="Phantom method check disabled",
|
|
846
|
-
severity=Severity.INFO,
|
|
847
|
-
message=(
|
|
848
|
-
"M-001 disabled — mypy not installed; install proofctl[dev] or "
|
|
849
|
-
"pip install mypy to enable phantom-method detection"
|
|
850
|
-
),
|
|
851
|
-
hint="pip install mypy # or: pip install proofctl[dev]",
|
|
852
|
-
authority="mypy attr-defined — AI calls methods that don't exist",
|
|
853
|
-
docs_url="https://mypy.readthedocs.io/en/stable/error_codes.html",
|
|
854
|
-
)
|
|
855
|
-
|
|
856
|
-
def _run_mypy(self, py_files: list[Path]) -> list[Finding]:
|
|
857
|
-
try:
|
|
858
|
-
result = subprocess.run(
|
|
859
|
-
[
|
|
860
|
-
"mypy",
|
|
861
|
-
"--ignore-missing-imports",
|
|
862
|
-
"--no-error-summary",
|
|
863
|
-
"--no-pretty",
|
|
864
|
-
*[str(p) for p in py_files],
|
|
865
|
-
],
|
|
866
|
-
capture_output=True,
|
|
867
|
-
text=True,
|
|
868
|
-
timeout=300,
|
|
869
|
-
)
|
|
870
|
-
except subprocess.TimeoutExpired:
|
|
871
|
-
return []
|
|
872
|
-
except FileNotFoundError:
|
|
873
|
-
raise
|
|
874
|
-
except OSError:
|
|
875
|
-
return []
|
|
876
|
-
|
|
877
|
-
# Index scoped files for quick membership check after path normalisation.
|
|
878
|
-
scoped = {str(p.resolve()) for p in py_files}
|
|
879
|
-
|
|
880
|
-
findings = []
|
|
881
|
-
for line in result.stdout.splitlines():
|
|
882
|
-
m = _MYPY_ATTR_RE.match(line)
|
|
883
|
-
if not m:
|
|
884
|
-
continue
|
|
885
|
-
|
|
886
|
-
if m.group(1):
|
|
887
|
-
file_path, lineno_str = m.group(1), m.group(2)
|
|
888
|
-
obj_type, attr = m.group(3), m.group(4)
|
|
889
|
-
msg = f"'{obj_type}' has no attribute '{attr}'"
|
|
890
|
-
else:
|
|
891
|
-
file_path, lineno_str = m.group(5), m.group(6)
|
|
892
|
-
attr = m.group(7)
|
|
893
|
-
msg = f"Module has no attribute '{attr}'"
|
|
894
|
-
|
|
895
|
-
# Skip findings for files outside the scan scope (e.g. installed libs).
|
|
896
|
-
try:
|
|
897
|
-
if str(Path(file_path).resolve()) not in scoped:
|
|
898
|
-
continue
|
|
899
|
-
except OSError:
|
|
900
|
-
continue
|
|
901
|
-
|
|
902
|
-
try:
|
|
903
|
-
lineno = int(lineno_str)
|
|
904
|
-
except ValueError:
|
|
905
|
-
lineno = None
|
|
906
|
-
|
|
907
|
-
findings.append(Finding(
|
|
908
|
-
file=file_path,
|
|
909
|
-
line=lineno,
|
|
910
|
-
col=None,
|
|
911
|
-
rule_id="PROOFCTL-M-001",
|
|
912
|
-
rule_name="Phantom method call",
|
|
913
|
-
severity=Severity.ERROR,
|
|
914
|
-
message=msg,
|
|
915
|
-
hint="Check the library's documentation for the correct method name.",
|
|
916
|
-
authority="mypy attr-defined — AI calls methods that don't exist",
|
|
917
|
-
docs_url="https://mypy.readthedocs.io/en/stable/error_codes.html",
|
|
918
|
-
))
|
|
919
|
-
|
|
920
|
-
return findings
|
|
@@ -1632,12 +1632,41 @@ class TerraformChecker(HclFileChecker):
|
|
|
1632
1632
|
))
|
|
1633
1633
|
return out
|
|
1634
1634
|
|
|
1635
|
+
# ── GKE Autopilot detection (0.5.6) ──────────────────────────────────
|
|
1636
|
+
#
|
|
1637
|
+
# GKE Autopilot is a managed-node mode where Google forces hardened
|
|
1638
|
+
# defaults that cannot be overridden. Many rules in this family target
|
|
1639
|
+
# `node_config` attributes that are illegal to set on Autopilot
|
|
1640
|
+
# clusters — flagging them produces false positives whose suggested
|
|
1641
|
+
# fix wouldn't even apply.
|
|
1642
|
+
#
|
|
1643
|
+
# The two helpers below let downstream rules skip themselves (or
|
|
1644
|
+
# demote severity) when scanning an Autopilot cluster. The detection
|
|
1645
|
+
# is whole-block string-match — adequate because the attribute name
|
|
1646
|
+
# is unambiguous.
|
|
1647
|
+
|
|
1648
|
+
@staticmethod
|
|
1649
|
+
def _is_gke_autopilot(b: HclBlock) -> bool:
|
|
1650
|
+
"""True iff the cluster has `enable_autopilot = true`."""
|
|
1651
|
+
if b.label1 != "google_container_cluster":
|
|
1652
|
+
return False
|
|
1653
|
+
val = (b.attr_value("enable_autopilot") or "").strip().lower()
|
|
1654
|
+
return val == "true"
|
|
1655
|
+
|
|
1656
|
+
@staticmethod
|
|
1657
|
+
def _has_workload_identity(b: HclBlock) -> bool:
|
|
1658
|
+
"""True iff `workload_identity_config { … }` block is nested."""
|
|
1659
|
+
return b.has_nested("workload_identity_config")
|
|
1660
|
+
|
|
1635
1661
|
def _g034(self, path: Path, blocks: list[HclBlock]) -> list[Finding]:
|
|
1636
1662
|
"""G-034 — GKE cluster missing explicit enable_legacy_abac = false."""
|
|
1637
1663
|
out = []
|
|
1638
1664
|
for b in blocks:
|
|
1639
1665
|
if b.kind != "resource" or b.label1 != "google_container_cluster":
|
|
1640
1666
|
continue
|
|
1667
|
+
# Autopilot: legacy ABAC is forced false and unchangeable.
|
|
1668
|
+
if self._is_gke_autopilot(b):
|
|
1669
|
+
continue
|
|
1641
1670
|
val = b.attr_value("enable_legacy_abac")
|
|
1642
1671
|
if val is None:
|
|
1643
1672
|
# Absent — provider default is false now, but old default was true.
|
|
@@ -1680,6 +1709,10 @@ class TerraformChecker(HclFileChecker):
|
|
|
1680
1709
|
for b in blocks:
|
|
1681
1710
|
if b.kind != "resource" or b.label1 not in ("google_container_cluster", "google_container_node_pool"):
|
|
1682
1711
|
continue
|
|
1712
|
+
# Autopilot: node_config is not settable and Autopilot forces
|
|
1713
|
+
# disable-legacy-endpoints = "true" by default. Skip.
|
|
1714
|
+
if b.label1 == "google_container_cluster" and self._is_gke_autopilot(b):
|
|
1715
|
+
continue
|
|
1683
1716
|
# Heuristic: require disable-legacy-endpoints = "true" anywhere in the block.
|
|
1684
1717
|
if not b.contains(r'disable-legacy-endpoints\s*=\s*"true"'):
|
|
1685
1718
|
out.append(self._f(
|
|
@@ -1699,6 +1732,10 @@ class TerraformChecker(HclFileChecker):
|
|
|
1699
1732
|
for b in blocks:
|
|
1700
1733
|
if b.kind != "resource" or b.label1 not in ("google_container_cluster", "google_container_node_pool"):
|
|
1701
1734
|
continue
|
|
1735
|
+
# Autopilot: shielded VM is forced on and node_config is not
|
|
1736
|
+
# settable. The suggested fix would even fail provider validation.
|
|
1737
|
+
if b.label1 == "google_container_cluster" and self._is_gke_autopilot(b):
|
|
1738
|
+
continue
|
|
1702
1739
|
has_block = b.contains(r'shielded_instance_config\s*\{')
|
|
1703
1740
|
has_secure = b.contains(r'enable_secure_boot\s*=\s*true')
|
|
1704
1741
|
has_integrity = b.contains(r'enable_integrity_monitoring\s*=\s*true')
|
|
@@ -1716,14 +1753,23 @@ class TerraformChecker(HclFileChecker):
|
|
|
1716
1753
|
|
|
1717
1754
|
def _g038(self, path: Path, blocks: list[HclBlock]) -> list[Finding]:
|
|
1718
1755
|
"""G-038 — GKE cluster without release_channel pinned to STABLE/REGULAR/RAPID."""
|
|
1719
|
-
|
|
1756
|
+
_channel_literal_re = re.compile(r'channel\s*=\s*"(STABLE|REGULAR|RAPID)"')
|
|
1757
|
+
# `channel = var.something` is also a valid pin — the value comes
|
|
1758
|
+
# from a typed variable, not a hardcoded mistake. Recognise it.
|
|
1759
|
+
_channel_var_re = re.compile(r'channel\s*=\s*var\.[a-zA-Z_][a-zA-Z0-9_]*')
|
|
1760
|
+
# `channel = local.something` similarly.
|
|
1761
|
+
_channel_local_re = re.compile(r'channel\s*=\s*local\.[a-zA-Z_][a-zA-Z0-9_]*')
|
|
1720
1762
|
out = []
|
|
1721
1763
|
for b in blocks:
|
|
1722
1764
|
if b.kind != "resource" or b.label1 != "google_container_cluster":
|
|
1723
1765
|
continue
|
|
1724
1766
|
body = "\n".join(b.raw_lines)
|
|
1725
1767
|
has_block = bool(re.search(r'release_channel\s*\{', body))
|
|
1726
|
-
has_channel =
|
|
1768
|
+
has_channel = (
|
|
1769
|
+
bool(_channel_literal_re.search(body))
|
|
1770
|
+
or bool(_channel_var_re.search(body))
|
|
1771
|
+
or bool(_channel_local_re.search(body))
|
|
1772
|
+
)
|
|
1727
1773
|
if not has_block or not has_channel:
|
|
1728
1774
|
out.append(self._f(
|
|
1729
1775
|
path, b.start_line,
|
|
@@ -1747,10 +1793,16 @@ class TerraformChecker(HclFileChecker):
|
|
|
1747
1793
|
continue
|
|
1748
1794
|
val = (b.attr_value("enable_shielded_nodes") or "").strip().lower()
|
|
1749
1795
|
if val != "true":
|
|
1796
|
+
# Autopilot: shielded nodes are forced on regardless. The
|
|
1797
|
+
# explicit attribute remains useful as documentation — demote
|
|
1798
|
+
# severity to INFO so the gate doesn't fail prod CI on it.
|
|
1799
|
+
severity = (
|
|
1800
|
+
Severity.INFO if self._is_gke_autopilot(b) else Severity.WARNING
|
|
1801
|
+
)
|
|
1750
1802
|
out.append(self._f(
|
|
1751
1803
|
path, b.attr_line("enable_shielded_nodes") or b.start_line,
|
|
1752
1804
|
"PROOFCTL-TF-G040", "GKE shielded nodes not enabled",
|
|
1753
|
-
|
|
1805
|
+
severity,
|
|
1754
1806
|
f'Cluster "{b.label2}" has enable_shielded_nodes not set to true',
|
|
1755
1807
|
hint="Set enable_shielded_nodes = true on the cluster.",
|
|
1756
1808
|
authority="CIS GKE Benchmark 6.5.5",
|
|
@@ -1765,6 +1817,17 @@ class TerraformChecker(HclFileChecker):
|
|
|
1765
1817
|
for b in blocks:
|
|
1766
1818
|
if b.kind != "resource" or b.label1 not in ("google_container_cluster", "google_container_node_pool"):
|
|
1767
1819
|
continue
|
|
1820
|
+
# Autopilot + Workload Identity: workloads get GCP API access via
|
|
1821
|
+
# Workload Identity bindings to GSAs, not via the node SA. The
|
|
1822
|
+
# node SA is mostly inert. Skip the warning when the cluster is
|
|
1823
|
+
# Autopilot AND has workload_identity_config — that's the
|
|
1824
|
+
# recommended Autopilot security model.
|
|
1825
|
+
if (
|
|
1826
|
+
b.label1 == "google_container_cluster"
|
|
1827
|
+
and self._is_gke_autopilot(b)
|
|
1828
|
+
and self._has_workload_identity(b)
|
|
1829
|
+
):
|
|
1830
|
+
continue
|
|
1768
1831
|
body = "\n".join(b.raw_lines)
|
|
1769
1832
|
uses_default = bool(_default_sa_re.search(body))
|
|
1770
1833
|
# Absence of any service_account inside node_config also implies default SA.
|
|
@@ -28,7 +28,7 @@ from .models import Finding, Severity
|
|
|
28
28
|
from .checkers.placeholders import PlaceholderChecker
|
|
29
29
|
from .checkers.leakage import LeakageChecker
|
|
30
30
|
from .checkers.quality import QualityChecker, AbstractionChecker
|
|
31
|
-
from .checkers.imports import ImportChecker,
|
|
31
|
+
from .checkers.imports import ImportChecker, DependencyChecker
|
|
32
32
|
from .checkers.variants import VariantChecker
|
|
33
33
|
from .checkers.security import SecurityChecker
|
|
34
34
|
from .checkers.llm_integration import LLMChecker
|
|
@@ -151,8 +151,6 @@ def _build_dir_checkers(config: ProofctlConfig, families: set[str] | None) -> li
|
|
|
151
151
|
checkers.append(DependencyChecker(
|
|
152
152
|
extra_high_risk=config.i002_extra_high_risk,
|
|
153
153
|
))
|
|
154
|
-
if _include("M"):
|
|
155
|
-
checkers.append(MethodChecker())
|
|
156
154
|
|
|
157
155
|
return checkers
|
|
158
156
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: proofctl
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.7
|
|
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
|
|
@@ -28,7 +28,7 @@ Dynamic: license-file
|
|
|
28
28
|
|
|
29
29
|
# proofctl
|
|
30
30
|
|
|
31
|
-
A linter built for the DevOps stack — Python, Terraform, Kubernetes, Helm, Dockerfiles, GitHub Actions, Shell — that catches the specific kinds of mistakes AI coding tools introduce. **
|
|
31
|
+
A linter built for the DevOps stack — Python, Terraform, Kubernetes, Helm, Dockerfiles, GitHub Actions, Shell — that catches the specific kinds of mistakes AI coding tools introduce. **268 rules**, zero dependencies, runs in under a second.
|
|
32
32
|
|
|
33
33
|
```
|
|
34
34
|
proofctl check .
|
|
@@ -172,7 +172,7 @@ git commit -m "chore: add proofctl baseline"
|
|
|
172
172
|
| Python — Quality (`Q`) | 17 | Mutable defaults, broad `except`, complexity, long functions, AI-slop patterns (echo docstrings, single-method classes, stale kwargs, defensive guards) |
|
|
173
173
|
| Python — Security (`S`) | 24 | OWASP Top-10 + CWE coverage: SQLi, command injection, weak crypto, JWT bypass, path traversal, XSS, SSRF, file upload, CSRF, missing authz, XPath injection, open redirect, TLS bypass |
|
|
174
174
|
| Secrets (`SECRET`) | 13 | AWS / GCP / GitHub / Slack / Stripe / private keys / JWTs / hardcoded passwords / K8s Secret YAML / connection strings with embedded creds |
|
|
175
|
-
| Python — Other |
|
|
175
|
+
| Python — Other | 11 | Placeholders, leakage (cross-language idioms, PII), import hallucination, variant detection, LLM guardrails (OWASP LLM Top-10) |
|
|
176
176
|
| Terraform — General + AI (`TF-T`, `TF-AI`) | 29 | required_version, sensitive outputs, IAM wildcards, mutable git refs, deprecated resources, AWS-region-in-GCP, placeholder values, image-family pinning, sensitive vars in VM metadata, unused variable declarations |
|
|
177
177
|
| Terraform — AWS (`TF-A`) | 40 | EC2 IMDSv2, S3 public-access-block, EBS encryption, VPC flow logs, KMS rotation, RDS hardening, SG / NACL / EKS / DynamoDB / ElastiCache / CloudTrail / IAM |
|
|
178
178
|
| Terraform — GCP (`TF-G`) | 52 | GKE hardening, GCS public-access prevention, IAM roles, KMS rotation, Cloud Function/Run public, BigQuery, Pub/Sub CMEK, Cloud SQL (TLS ssl_mode aware), firewall ingress, compute access scopes, Secret Manager CMEK consistency |
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "proofctl"
|
|
7
|
-
version = "0.5.
|
|
7
|
+
version = "0.5.7"
|
|
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" }
|
|
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
|