proofctl 0.6.0__tar.gz → 0.6.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.
Files changed (41) hide show
  1. {proofctl-0.6.0/proofctl.egg-info → proofctl-0.6.1}/PKG-INFO +5 -5
  2. {proofctl-0.6.0 → proofctl-0.6.1}/README.md +4 -4
  3. {proofctl-0.6.0 → proofctl-0.6.1}/proofctl/checkers/terraform.py +85 -0
  4. {proofctl-0.6.0 → proofctl-0.6.1}/proofctl/checkers/yaml_checker.py +112 -0
  5. {proofctl-0.6.0 → proofctl-0.6.1/proofctl.egg-info}/PKG-INFO +5 -5
  6. {proofctl-0.6.0 → proofctl-0.6.1}/pyproject.toml +6 -4
  7. {proofctl-0.6.0 → proofctl-0.6.1}/LICENSE +0 -0
  8. {proofctl-0.6.0 → proofctl-0.6.1}/MANIFEST.in +0 -0
  9. {proofctl-0.6.0 → proofctl-0.6.1}/proofctl/__init__.py +0 -0
  10. {proofctl-0.6.0 → proofctl-0.6.1}/proofctl/_globmatch.py +0 -0
  11. {proofctl-0.6.0 → proofctl-0.6.1}/proofctl/baseline.py +0 -0
  12. {proofctl-0.6.0 → proofctl-0.6.1}/proofctl/checkers/__init__.py +0 -0
  13. {proofctl-0.6.0 → proofctl-0.6.1}/proofctl/checkers/base.py +0 -0
  14. {proofctl-0.6.0 → proofctl-0.6.1}/proofctl/checkers/dockerfile.py +0 -0
  15. {proofctl-0.6.0 → proofctl-0.6.1}/proofctl/checkers/hcl_utils.py +0 -0
  16. {proofctl-0.6.0 → proofctl-0.6.1}/proofctl/checkers/imports.py +0 -0
  17. {proofctl-0.6.0 → proofctl-0.6.1}/proofctl/checkers/leakage.py +0 -0
  18. {proofctl-0.6.0 → proofctl-0.6.1}/proofctl/checkers/llm_integration.py +0 -0
  19. {proofctl-0.6.0 → proofctl-0.6.1}/proofctl/checkers/placeholders.py +0 -0
  20. {proofctl-0.6.0 → proofctl-0.6.1}/proofctl/checkers/quality.py +0 -0
  21. {proofctl-0.6.0 → proofctl-0.6.1}/proofctl/checkers/secrets.py +0 -0
  22. {proofctl-0.6.0 → proofctl-0.6.1}/proofctl/checkers/security.py +0 -0
  23. {proofctl-0.6.0 → proofctl-0.6.1}/proofctl/checkers/shell_checker.py +0 -0
  24. {proofctl-0.6.0 → proofctl-0.6.1}/proofctl/checkers/terragrunt.py +0 -0
  25. {proofctl-0.6.0 → proofctl-0.6.1}/proofctl/checkers/variants.py +0 -0
  26. {proofctl-0.6.0 → proofctl-0.6.1}/proofctl/cli.py +0 -0
  27. {proofctl-0.6.0 → proofctl-0.6.1}/proofctl/config.py +0 -0
  28. {proofctl-0.6.0 → proofctl-0.6.1}/proofctl/engine.py +0 -0
  29. {proofctl-0.6.0 → proofctl-0.6.1}/proofctl/fixer.py +0 -0
  30. {proofctl-0.6.0 → proofctl-0.6.1}/proofctl/models.py +0 -0
  31. {proofctl-0.6.0 → proofctl-0.6.1}/proofctl/registry.py +0 -0
  32. {proofctl-0.6.0 → proofctl-0.6.1}/proofctl/reporters/__init__.py +0 -0
  33. {proofctl-0.6.0 → proofctl-0.6.1}/proofctl/reporters/html_reporter.py +0 -0
  34. {proofctl-0.6.0 → proofctl-0.6.1}/proofctl/reporters/json_reporter.py +0 -0
  35. {proofctl-0.6.0 → proofctl-0.6.1}/proofctl/reporters/terminal.py +0 -0
  36. {proofctl-0.6.0 → proofctl-0.6.1}/proofctl.egg-info/SOURCES.txt +0 -0
  37. {proofctl-0.6.0 → proofctl-0.6.1}/proofctl.egg-info/dependency_links.txt +0 -0
  38. {proofctl-0.6.0 → proofctl-0.6.1}/proofctl.egg-info/entry_points.txt +0 -0
  39. {proofctl-0.6.0 → proofctl-0.6.1}/proofctl.egg-info/requires.txt +0 -0
  40. {proofctl-0.6.0 → proofctl-0.6.1}/proofctl.egg-info/top_level.txt +0 -0
  41. {proofctl-0.6.0 → proofctl-0.6.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: proofctl
3
- Version: 0.6.0
3
+ Version: 0.6.1
4
4
  Summary: DevOps linter and security scanner for AI slop and misconfigurations — runs in pre-commit, CI, or post-commit; no external tools required
5
5
  Author: Kolawolu Odunola
6
6
  License: MIT
@@ -35,7 +35,7 @@ Dynamic: license-file
35
35
 
36
36
  # proofctl
37
37
 
38
- A static-analysis linter and security scanner for the DevOps stack — Python, Terraform, Kubernetes, Helm, Dockerfiles, GitHub Actions, Shell — that catches the specific kinds of mistakes AI coding tools introduce. **267 rules**, no external tools, runs in under a second.
38
+ A static-analysis linter and security scanner for the DevOps stack — Python, Terraform, Kubernetes, Helm, Dockerfiles, GitHub Actions, Shell — that catches the specific kinds of mistakes AI coding tools introduce. **271 rules**, no external tools, runs in under a second.
39
39
 
40
40
  ```
41
41
  proofctl check .
@@ -172,7 +172,7 @@ git commit -m "chore: add proofctl baseline"
172
172
 
173
173
  ## Rules
174
174
 
175
- 267 rules across 13 families. Run `proofctl rules` after install to enumerate.
175
+ 271 rules across 13 families. Run `proofctl rules` after install to enumerate.
176
176
 
177
177
  | Family | Count | What it covers |
178
178
  |---|---|---|
@@ -182,12 +182,12 @@ git commit -m "chore: add proofctl baseline"
182
182
  | Python — Other | 15 | Placeholders, leakage (cross-language idioms, PII), import hallucination, variant detection, LLM guardrails (OWASP LLM Top-10) |
183
183
  | Terraform — General + AI (`TF-T`, `TF-AI`) | 25 | 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 |
184
184
  | Terraform — AWS (`TF-A`) | 36 | EC2 IMDSv2, S3 public-access-block, EBS encryption, VPC flow logs, KMS rotation, RDS hardening, SG / NACL / EKS / DynamoDB / ElastiCache / CloudTrail / IAM |
185
- | Terraform — GCP (`TF-G`) | 46 | 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 |
185
+ | Terraform — GCP (`TF-G`) | 48 | 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 |
186
186
  | Terraform — Azure (`TF-AZ`) | 14 | Storage public blob, SQL public network, AKS RBAC, Key Vault purge protection, NSG SSH/RDP, managed disk CMK |
187
187
  | Terraform — Mechanics + Lifecycle (`TF-M`, `TF-V`) | 10 | SG inline mixing, `force_destroy`, mutable module refs, lifecycle antipatterns |
188
188
  | Terragrunt (`TG`) | 6 | HCL inputs, mock_outputs, remote state encryption |
189
189
  | Dockerfile (`DF`) | 15 | Latest tag, root user, secret leakage, OCI labels, HEALTHCHECK, legacy ENV form |
190
- | Kubernetes YAML (`YAML`) | 32 | Pod Security Standards, hostPath, default namespace, image digest pinning, RBAC wildcards |
190
+ | Kubernetes YAML (`YAML`) | 34 | Pod Security Standards, hostPath, default namespace, image digest pinning, RBAC wildcards |
191
191
  | GitHub Actions (`YAML-GHA`) | 6 | SHA pinning, expression injection, missing permissions, secret env-var leakage, missing timeouts |
192
192
  | Shell (`SH`) | 8 | Dangerous globbing, missing `set -euo pipefail` |
193
193
 
@@ -1,6 +1,6 @@
1
1
  # proofctl
2
2
 
3
- A static-analysis linter and security scanner for the DevOps stack — Python, Terraform, Kubernetes, Helm, Dockerfiles, GitHub Actions, Shell — that catches the specific kinds of mistakes AI coding tools introduce. **267 rules**, no external tools, runs in under a second.
3
+ A static-analysis linter and security scanner for the DevOps stack — Python, Terraform, Kubernetes, Helm, Dockerfiles, GitHub Actions, Shell — that catches the specific kinds of mistakes AI coding tools introduce. **271 rules**, no external tools, runs in under a second.
4
4
 
5
5
  ```
6
6
  proofctl check .
@@ -137,7 +137,7 @@ git commit -m "chore: add proofctl baseline"
137
137
 
138
138
  ## Rules
139
139
 
140
- 267 rules across 13 families. Run `proofctl rules` after install to enumerate.
140
+ 271 rules across 13 families. Run `proofctl rules` after install to enumerate.
141
141
 
142
142
  | Family | Count | What it covers |
143
143
  |---|---|---|
@@ -147,12 +147,12 @@ git commit -m "chore: add proofctl baseline"
147
147
  | Python — Other | 15 | 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`) | 25 | 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`) | 36 | EC2 IMDSv2, S3 public-access-block, EBS encryption, VPC flow logs, KMS rotation, RDS hardening, SG / NACL / EKS / DynamoDB / ElastiCache / CloudTrail / IAM |
150
- | Terraform — GCP (`TF-G`) | 46 | 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 |
150
+ | Terraform — GCP (`TF-G`) | 48 | 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 |
151
151
  | Terraform — Azure (`TF-AZ`) | 14 | Storage public blob, SQL public network, AKS RBAC, Key Vault purge protection, NSG SSH/RDP, managed disk CMK |
152
152
  | Terraform — Mechanics + Lifecycle (`TF-M`, `TF-V`) | 10 | SG inline mixing, `force_destroy`, mutable module refs, lifecycle antipatterns |
153
153
  | Terragrunt (`TG`) | 6 | HCL inputs, mock_outputs, remote state encryption |
154
154
  | Dockerfile (`DF`) | 15 | Latest tag, root user, secret leakage, OCI labels, HEALTHCHECK, legacy ENV form |
155
- | Kubernetes YAML (`YAML`) | 32 | Pod Security Standards, hostPath, default namespace, image digest pinning, RBAC wildcards |
155
+ | Kubernetes YAML (`YAML`) | 34 | Pod Security Standards, hostPath, default namespace, image digest pinning, RBAC wildcards |
156
156
  | GitHub Actions (`YAML-GHA`) | 6 | SHA pinning, expression injection, missing permissions, secret env-var leakage, missing timeouts |
157
157
  | Shell (`SH`) | 8 | Dangerous globbing, missing `set -euo pipefail` |
158
158
 
@@ -121,6 +121,30 @@ _BROAD_GCP_ROLES = frozenset({
121
121
  "roles/iam.serviceAccountAdmin",
122
122
  })
123
123
 
124
+ # Project-scoped roles that grant data-plane access to EVERY resource of that
125
+ # service in the project. Resource-level IAM exists for all of them, so a
126
+ # project-wide grant is the cross-tenant blast-radius shape: one compromised
127
+ # principal reads (or rewrites) every tenant's data.
128
+ _PROJECT_WIDE_DATA_ROLES = frozenset({
129
+ "roles/secretmanager.admin",
130
+ "roles/secretmanager.secretAccessor",
131
+ "roles/secretmanager.secretVersionManager",
132
+ "roles/datastore.owner",
133
+ "roles/datastore.user",
134
+ "roles/datastore.viewer",
135
+ "roles/storage.objectAdmin",
136
+ "roles/storage.objectUser",
137
+ "roles/storage.objectViewer",
138
+ "roles/cloudkms.cryptoKeyDecrypter",
139
+ "roles/cloudkms.cryptoKeyEncrypterDecrypter",
140
+ "roles/bigquery.dataOwner",
141
+ "roles/bigquery.dataEditor",
142
+ "roles/bigquery.dataViewer",
143
+ })
144
+
145
+ # Any `roles/<service>.admin` at project scope (storage.admin, cloudsql.admin…).
146
+ _SERVICE_ADMIN_ROLE_RE = re.compile(r"^roles/[a-z][A-Za-z]*\.admin$")
147
+
124
148
  _GCP_IAM_RESOURCES = frozenset({
125
149
  "google_project_iam_binding",
126
150
  "google_project_iam_member",
@@ -247,6 +271,8 @@ class TerraformChecker(HclFileChecker):
247
271
  out += self._g019(path, blocks)
248
272
  out += self._g020(path, blocks)
249
273
  out += self._g021(path, blocks)
274
+ out += self._g049(path, blocks)
275
+ out += self._g050(path, blocks)
250
276
  out += self._g023(path, blocks)
251
277
  out += self._g024(path, blocks)
252
278
  out += self._g025(path, blocks)
@@ -1359,6 +1385,65 @@ class TerraformChecker(HclFileChecker):
1359
1385
  ))
1360
1386
  return out
1361
1387
 
1388
+ def _g049(self, path: Path, blocks: list[HclBlock]) -> list[Finding]:
1389
+ """G-049 — Project-scoped role granting data access to every resource."""
1390
+ out = []
1391
+ for b in blocks:
1392
+ if b.kind != "resource" or b.label1 not in _GCP_IAM_RESOURCES:
1393
+ continue
1394
+ role = (b.attr_value("role") or "").strip('"')
1395
+ if not role or role in _BROAD_GCP_ROLES:
1396
+ continue # G-021 already covers the primitive/IAM-admin roles
1397
+ if role not in _PROJECT_WIDE_DATA_ROLES and not _SERVICE_ADMIN_ROLE_RE.match(role):
1398
+ continue
1399
+ out.append(self._f(
1400
+ path, b.attr_line("role") or b.start_line,
1401
+ "PROOFCTL-TF-G049", "Project-wide data-access role",
1402
+ Severity.WARNING,
1403
+ f'"{b.label1}" grants "{role}" at project scope — this principal can '
1404
+ f"reach every resource of that service in the project",
1405
+ hint=(
1406
+ "Bind the role on the specific resource instead — e.g. "
1407
+ "google_secret_manager_secret_iam_member / "
1408
+ "google_storage_bucket_iam_member — so one compromised principal "
1409
+ "cannot read every tenant's data."
1410
+ ),
1411
+ authority="Google IAM — principle of least privilege",
1412
+ docs_url="https://cloud.google.com/iam/docs/best-practices-service-accounts",
1413
+ ))
1414
+ return out
1415
+
1416
+ def _g050(self, path: Path, blocks: list[HclBlock]) -> list[Finding]:
1417
+ """G-050 — Firewall rule allows unrestricted egress to the internet."""
1418
+ out = []
1419
+ for b in blocks:
1420
+ if b.kind != "resource" or b.label1 != "google_compute_firewall":
1421
+ continue
1422
+ if (b.attr_value("direction") or "").strip('"').upper() != "EGRESS":
1423
+ continue
1424
+ if not b.contains(r'destination_ranges\s*=\s*\[[^\]]*"0\.0\.0\.0/0"'):
1425
+ continue
1426
+ # A deny-all egress rule to 0.0.0.0/0 is the protective pattern, not a
1427
+ # finding. Match single-line and multi-line block forms.
1428
+ if b.has_nested("deny") or b.contains(r"\bdeny\s*\{"):
1429
+ continue
1430
+ if not (b.has_nested("allow") or b.contains(r"\ballow\s*\{")):
1431
+ continue
1432
+ out.append(self._f(
1433
+ path, b.start_line,
1434
+ "PROOFCTL-TF-G050", "Firewall allows egress to the internet",
1435
+ Severity.WARNING,
1436
+ f'google_compute_firewall "{b.label2}" allows egress to 0.0.0.0/0 — '
1437
+ f"a compromised workload can exfiltrate data anywhere",
1438
+ hint=(
1439
+ "Restrict destination_ranges to the CIDRs actually required, or keep "
1440
+ "an FQDN/CIDR allow-list above a low-priority deny-all egress rule."
1441
+ ),
1442
+ authority="CIS GCP Benchmark 3.x / MITRE ATT&CK T1048",
1443
+ docs_url="https://cloud.google.com/vpc/docs/firewalls",
1444
+ ))
1445
+ return out
1446
+
1362
1447
  def _g023(self, path: Path, blocks: list[HclBlock]) -> list[Finding]:
1363
1448
  """G-023 — KMS key rotation not configured or > 90 days."""
1364
1449
  out = []
@@ -812,6 +812,116 @@ def _yaml014_k8s(root: yaml.MappingNode, path: Path) -> list[Finding]:
812
812
  return findings
813
813
 
814
814
 
815
+ def _netpol_meta(root: yaml.MappingNode) -> tuple[str, yaml.MappingNode | None]:
816
+ """Return (name, spec) for a NetworkPolicy document, or ("", None)."""
817
+ if _scalar(_get_key(root, "kind")) != "NetworkPolicy":
818
+ return "", None
819
+ name = "unnamed"
820
+ meta = _get_key(root, "metadata")
821
+ if isinstance(meta, yaml.MappingNode):
822
+ name = _scalar(_get_key(meta, "name")) or "unnamed"
823
+ spec = _get_key(root, "spec")
824
+ return name, spec if isinstance(spec, yaml.MappingNode) else None
825
+
826
+
827
+ def _yaml027_netpol_no_egress(root: yaml.MappingNode, path: Path) -> list[Finding]:
828
+ """PROOFCTL-YAML-027 — NetworkPolicy does not restrict egress.
829
+
830
+ Omitting `Egress` from policyTypes means Kubernetes defaults to
831
+ `policyTypes: [Ingress]`, leaving outbound traffic completely
832
+ unrestricted — the pod can still reach the cloud metadata endpoint
833
+ and any internet host.
834
+ """
835
+ name, spec = _netpol_meta(root)
836
+ if spec is None:
837
+ return []
838
+
839
+ types_node = _get_key(spec, "policyTypes")
840
+ declared = []
841
+ if isinstance(types_node, yaml.SequenceNode):
842
+ declared = [_scalar(v) or "" for v in types_node.value]
843
+ if "Egress" in declared:
844
+ return []
845
+ # An explicit `egress:` block implies Egress even if policyTypes is absent.
846
+ if _get_key(spec, "egress") is not None:
847
+ return []
848
+
849
+ node = types_node or spec
850
+ return [Finding(
851
+ file=str(path),
852
+ line=node.start_mark.line + 1,
853
+ col=node.start_mark.column,
854
+ rule_id="PROOFCTL-YAML-027",
855
+ rule_name="NetworkPolicy does not restrict egress",
856
+ severity=Severity.WARNING,
857
+ message=(
858
+ f"NetworkPolicy '{name}' has no Egress policyType — outbound traffic is "
859
+ f"unrestricted, so a compromised pod can reach the metadata endpoint and "
860
+ f"exfiltrate to any host"
861
+ ),
862
+ hint=(
863
+ "Add 'Egress' to policyTypes and an egress allow-list (DNS, the datastore "
864
+ "CIDR, required APIs). Default-deny egress also blocks 169.254.169.254."
865
+ ),
866
+ authority="Kubernetes Network Policies / NSA-CISA Kubernetes Hardening",
867
+ docs_url="https://kubernetes.io/docs/concepts/services-networking/network-policies/",
868
+ )]
869
+
870
+
871
+ def _yaml028_netpol_broad_peer(root: yaml.MappingNode, path: Path) -> list[Finding]:
872
+ """PROOFCTL-YAML-028 — NetworkPolicy peer selects a whole namespace, all ports.
873
+
874
+ A `from`/`to` peer carrying only a namespaceSelector admits every pod in any
875
+ matching namespace. With no `ports:` on the rule it is every port too, so any
876
+ workload landing in that namespace reaches the datastore tier directly.
877
+ """
878
+ name, spec = _netpol_meta(root)
879
+ if spec is None:
880
+ return []
881
+
882
+ findings: list[Finding] = []
883
+ for direction, peer_key in (("ingress", "from"), ("egress", "to")):
884
+ rules_node = _get_key(spec, direction)
885
+ if not isinstance(rules_node, yaml.SequenceNode):
886
+ continue
887
+ for rule in rules_node.value:
888
+ if not isinstance(rule, yaml.MappingNode):
889
+ continue
890
+ has_ports = _get_key(rule, "ports") is not None
891
+ peers = _get_key(rule, peer_key)
892
+ if not isinstance(peers, yaml.SequenceNode):
893
+ continue
894
+ for peer in peers.value:
895
+ if not isinstance(peer, yaml.MappingNode):
896
+ continue
897
+ keys = {_scalar(k) for k, _ in peer.value}
898
+ # Only a namespaceSelector: no pod narrowing within the namespace.
899
+ if "namespaceSelector" not in keys or "podSelector" in keys:
900
+ continue
901
+ if has_ports:
902
+ continue # port-scoped: materially narrower, don't flag
903
+ findings.append(Finding(
904
+ file=str(path),
905
+ line=peer.start_mark.line + 1,
906
+ col=peer.start_mark.column,
907
+ rule_id="PROOFCTL-YAML-028",
908
+ rule_name="NetworkPolicy peer selects entire namespace",
909
+ severity=Severity.WARNING,
910
+ message=(
911
+ f"NetworkPolicy '{name}' {direction} rule admits every pod in the "
912
+ f"selected namespace on every port"
913
+ ),
914
+ hint=(
915
+ "Pair the namespaceSelector with a podSelector in the same peer "
916
+ "(logical AND) and add a 'ports:' list, so only the intended "
917
+ "workload and port are reachable."
918
+ ),
919
+ authority="Kubernetes Network Policies / NSA-CISA Kubernetes Hardening",
920
+ docs_url="https://kubernetes.io/docs/concepts/services-networking/network-policies/",
921
+ ))
922
+ return findings
923
+
924
+
815
925
  def _yaml015_k8s(root: yaml.MappingNode, path: Path) -> list[Finding]:
816
926
  """PROOFCTL-YAML-015 — ClusterRoleBinding with roleRef.name: cluster-admin."""
817
927
  findings: list[Finding] = []
@@ -1793,6 +1903,8 @@ class _YamlCheckerImpl(YamlFileChecker):
1793
1903
  findings.extend(_yaml024_runasuser_low(root, path))
1794
1904
  findings.extend(_yaml025_rbac_wildcard_admin(root, path))
1795
1905
  findings.extend(_yaml026_net_raw(root, path))
1906
+ findings.extend(_yaml027_netpol_no_egress(root, path))
1907
+ findings.extend(_yaml028_netpol_broad_peer(root, path))
1796
1908
  # AI-slop: removed apiVersions, PSP, explicit root, deprecated annotations
1797
1909
  findings.extend(_yaml_ai001_k8s_removed_api(root, path))
1798
1910
  findings.extend(_yaml_ai002_pod_security_policy(root, path))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: proofctl
3
- Version: 0.6.0
3
+ Version: 0.6.1
4
4
  Summary: DevOps linter and security scanner for AI slop and misconfigurations — runs in pre-commit, CI, or post-commit; no external tools required
5
5
  Author: Kolawolu Odunola
6
6
  License: MIT
@@ -35,7 +35,7 @@ Dynamic: license-file
35
35
 
36
36
  # proofctl
37
37
 
38
- A static-analysis linter and security scanner for the DevOps stack — Python, Terraform, Kubernetes, Helm, Dockerfiles, GitHub Actions, Shell — that catches the specific kinds of mistakes AI coding tools introduce. **267 rules**, no external tools, runs in under a second.
38
+ A static-analysis linter and security scanner for the DevOps stack — Python, Terraform, Kubernetes, Helm, Dockerfiles, GitHub Actions, Shell — that catches the specific kinds of mistakes AI coding tools introduce. **271 rules**, no external tools, runs in under a second.
39
39
 
40
40
  ```
41
41
  proofctl check .
@@ -172,7 +172,7 @@ git commit -m "chore: add proofctl baseline"
172
172
 
173
173
  ## Rules
174
174
 
175
- 267 rules across 13 families. Run `proofctl rules` after install to enumerate.
175
+ 271 rules across 13 families. Run `proofctl rules` after install to enumerate.
176
176
 
177
177
  | Family | Count | What it covers |
178
178
  |---|---|---|
@@ -182,12 +182,12 @@ git commit -m "chore: add proofctl baseline"
182
182
  | Python — Other | 15 | Placeholders, leakage (cross-language idioms, PII), import hallucination, variant detection, LLM guardrails (OWASP LLM Top-10) |
183
183
  | Terraform — General + AI (`TF-T`, `TF-AI`) | 25 | 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 |
184
184
  | Terraform — AWS (`TF-A`) | 36 | EC2 IMDSv2, S3 public-access-block, EBS encryption, VPC flow logs, KMS rotation, RDS hardening, SG / NACL / EKS / DynamoDB / ElastiCache / CloudTrail / IAM |
185
- | Terraform — GCP (`TF-G`) | 46 | 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 |
185
+ | Terraform — GCP (`TF-G`) | 48 | 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 |
186
186
  | Terraform — Azure (`TF-AZ`) | 14 | Storage public blob, SQL public network, AKS RBAC, Key Vault purge protection, NSG SSH/RDP, managed disk CMK |
187
187
  | Terraform — Mechanics + Lifecycle (`TF-M`, `TF-V`) | 10 | SG inline mixing, `force_destroy`, mutable module refs, lifecycle antipatterns |
188
188
  | Terragrunt (`TG`) | 6 | HCL inputs, mock_outputs, remote state encryption |
189
189
  | Dockerfile (`DF`) | 15 | Latest tag, root user, secret leakage, OCI labels, HEALTHCHECK, legacy ENV form |
190
- | Kubernetes YAML (`YAML`) | 32 | Pod Security Standards, hostPath, default namespace, image digest pinning, RBAC wildcards |
190
+ | Kubernetes YAML (`YAML`) | 34 | Pod Security Standards, hostPath, default namespace, image digest pinning, RBAC wildcards |
191
191
  | GitHub Actions (`YAML-GHA`) | 6 | SHA pinning, expression injection, missing permissions, secret env-var leakage, missing timeouts |
192
192
  | Shell (`SH`) | 8 | Dangerous globbing, missing `set -euo pipefail` |
193
193
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "proofctl"
7
- version = "0.6.0"
7
+ version = "0.6.1"
8
8
  description = "DevOps linter and security scanner for AI slop and misconfigurations — runs in pre-commit, CI, or post-commit; no external tools required"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -59,9 +59,11 @@ target-version = "py311"
59
59
  line-length = 120
60
60
 
61
61
  [tool.ruff.lint]
62
- # Default rules (pycodestyle errors E4/E7/E9 + pyflakes F) catch the real bug
63
- # class: undefined names, unused imports/variables. E701 (compact one-line
64
- # `if cond: return`) is an intentional style in the type-dispatch helpers.
62
+ # Pinned explicitly, NOT left to ruff's default: the default set changes between
63
+ # ruff versions, and `ruff>=0.4` in [dev] would then silently move a hard CI gate.
64
+ # E4/E7/E9 + F are the bug-class rules (undefined names, unused imports/vars).
65
+ select = ["E4", "E7", "E9", "F"]
66
+ # E701 (compact one-line `if cond: return`) is intentional in the type-dispatch helpers.
65
67
  ignore = ["E701"]
66
68
 
67
69
  [tool.setuptools.packages.find]
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