Home / Stories / Security & compliance posture
Story · Security In development

A posture report your CISO can read, that never says "compliant" without proof.

Before an audit someone asks: "are our workloads non-root, are service account tokens locked down, is there a network policy everywhere?" The usual answer is a spreadsheet and a lot of hope. The assistant answers from the manifests themselves, says "unknown" when it cannot prove something, and runs a set of deterministic posture checks on the systems around your clusters.

Starts fromYour GitOps repositories, plus read-only access to Cloudflare, GitLab and the GKE recommender.
Looks atEvery workload manifest, edge zone settings and DNS, forge settings and CI, Google's own recommendations, chart signatures.
You getAn on-demand PDF mapped to control IDs, and security issues, one finding per cause, each with the evidence.
01 · Ask for it

One command, one PDF.

The posture report is generated on demand. It walks every manifest in every writable repository, the same corpus the repo scanner sees, and renders a PDF.

It creates no merge requests and no issues. The gaps it counts are already proposed by the other agents; the report is the summary, not a second copy of the work.

02 · Map manifests to controls

Three controls, chosen because they can be proven.

The first version covers the controls where a manifest can carry positive evidence:

  • PSS-restricted:runAsNonRoot, every container, init and ephemeral container included
  • CIS-5.1.x:sa-token-least-privilege, service account token automount
  • NSA-hardening:NetworkPolicy-present, checked against a repo-wide index so a policy in a sibling file still counts
03 · Never falsely compliant

Silence is "unknown", not "satisfied".

A false "compliant" is worse than a false gap: someone may rely on it in an audit. So a control is satisfied only when the manifest says so explicitly. No finding is not proof. Anything the checks cannot decide, including an error while checking, is counted as unknown.

The report says on its face: manifest-checkable subset only, not an attestation.

04 · The report

Satisfied, gap, unknown, and the denominator.

Each control shows how many workloads were scanned and how they split. A reader sees at once that 140 of 212 workloads prove they run as non-root, 31 do not, and 41 could not be decided from the manifest alone.

ISO 27001 and SOC 2 are organisational standards; mapping down to them is planned as a later layer.

05 · Your edge

Cloudflare zones, checked every day.

Read-only, per zone: minimum TLS version, HSTS, SSL mode, DNSSEC, managed WAF rules and rate limiting, plus dangling CNAMEs that point at a target that no longer resolves, a classic takeover risk.

A zone whose plan does not include WAF or rate limiting abstains rather than fires. A setting that could not be read is reported as unobserved, never as fine. A separate check catches an armed edge block that would refuse your own clusters' egress IPs.

06 · Your forge

The GitLab instance your GitOps depends on.

If someone can force-push to the deployment repo, the rest of the chain does not matter. The assistant reviews the forge itself:

  • Access: unprotected default branches, force push allowed, merge gates off, too many owners, groups without 2FA, memberships with no expiry.
  • CI: committed secrets, debug tracing that prints variables, public job artifacts, over-broad job tokens.
  • Runners: shared runners unlocked on protected work, runners offline for months.
07 · Google's view of your GKE clusters

The recommender, quoted word for word.

Every active GKE recommendation is read per cluster. The text is Google's, never paraphrased. Recommendations the assistant already covers itself (disruption budgets, memory limits, under-provisioned workloads) are attached as supporting evidence instead of being filed twice.

New recommendations arrive as issues. Turning them into a draft MR against the matching manifest is built but not yet switched on.

08 · Chart signatures and pinned digests

Only verify what is actually signed. Only pin what was actually read.

Before a Flux spec.verify block is proposed, the chart's signing is checked with cosign and cached. A verify block on a chart that is not cosign-signed would break the release, so it is refused.

When a proposal pins an image to a @sha256: digest, that digest needs a receipt from the registry. A digest nobody looked up is held for review, never shipped.

09 · Issues by default

Posture findings describe a setting, not a diff.

Edge, forge and cloud settings mostly live outside your git repos, so these findings are issues: one per cause, with the evidence, the risk and the fix steps. Where the setting does live in git (a CI file, a manifest), a draft MR is possible.

The full issue is below.

terminal
$ gitops-assistant compliance-report --output posture.pdf
walking 4 repositories · 1,806 manifests
212 workloads · 3 controls
PSS-restricted:runAsNonRoot            140 / 31 / 41
CIS-5.1.x:sa-token-least-privilege     96 / 88 / 28
NSA-hardening:NetworkPolicy-present    171 / 12 / 29
wrote posture.pdf · 0 proposals created
satisfied / gap / unknown · illustrative numbers
apps/shop/orders-api/deployment.yaml
spec:
  template:
    spec:
      automountServiceAccountToken: false   # CIS-5.1.x ✓
      securityContext:
        runAsNonRoot: true                 # PSS runAsNonRoot ✓
      initContainers:
        - name: migrate
          securityContext:
            runAsUser: 0                   # overrides the pod → gap
      containers:
        - name: app
  • PSS-restricted:runAsNonRoot init container migrate runs as root → gap
  • CIS-5.1.x:sa-token-least-privilege explicit false → satisfied
  • NSA-hardening:NetworkPolicy-present networkpolicy_shop-default.yaml (sibling file) → satisfied
verdict rule
  • runAsNonRoot: true present on pod and not overridden SATISFIED
  • runAsUser: 0 on any container GAP
  • field absent, image decides the user UNKNOWN
  • chart-rendered values not visible in git UNKNOWN
  • checker raised an error UNKNOWN
absence of a finding is never counted as compliance
Kubernetes manifest posture · Acme
4 repositories · 212 workloads · generated 2026-09-24
ControlStandardSatisfiedGapUnknown
runAsNonRootPSS restricted1403141
SA token least privilegeCIS 5.1.x968828
NetworkPolicy presentNSA/CISA1711229
runAsNonRoot · 212 workloads
Manifest-checkable subset only, not an attestation. "Satisfied" requires positive evidence in the manifest; anything undecidable is "Unknown".
Illustrative page from the PDF.
Cloudflare · zone shop.example · read-only
  • minimum TLS 1.0 → high
  • HSTS disabled → medium
  • SSL mode full (strict)
  • DNSSEC disabled → medium
  • managed WAF not on this plan → abstain
  • CNAME old-promo.shop.example → promo-2023.pages-host.example · NXDOMAIN → dangling
6 surfaces read · 4 findings · 1 abstention · 0 unreadable
GitLab review · group acme/deployments
  • acme/deployments/prod force push allowed on main
  • group acme 2FA not required
  • group acme 9 owners · 14 members without expiry
  • acme/platform CI CI_DEBUG_TRACE: "true" in .gitlab-ci.yml
  • acme/platform CI artifacts public on a job that dumps env
  • runner build-02 offline 94 days
  • shared runners not used by protected pipelines
GKE recommender · cluster prod-eu
  • PDB_UNPROTECTED_STATEFULSET · shop/orders-db already covered · attached as evidence
  • MEMORY_LIMIT_NOT_SET · shop/cart already covered · attached as evidence
  • new subtype · platform/ingress → issue, Google's text verbatim
# quoted, never paraphrased
"<Google's recommendation text, exactly as returned>"
unchanged recommendation (same etag) → never re-filed
signature posture · cached 7 days
  • oci://ghcr.io/acme-charts/orders cosign signed → spec.verify: {provider: cosign}
  • https://charts.example.org · redis .prov only → no verify block
  • proposed verify block on unsigned chart refused → held for review
digest receipt gate
+  image: registry.example.com/shop/cart@sha256:9c1e…
✓ receipt: resolved from registry at emission
+  image: sidecar/labeler@sha256:<digest>
✕ no registry receipt · held (NEEDS_REVIEW)
Issue · shop.example: minimum TLS version is 1.0
category:securitysource:cloud_posturerisk:high
  • Check TLS 1.0/1.1 client share in edge analytics
  • SSL/TLS → Edge Certificates → Minimum TLS Version → 1.2
↓ the full issue is below

The issue you get.

Illustrative, shaped like the edge-posture issues the assistant files today. Settings outside git become issues; nothing here is changed for you.

Open issue

Cloudflare zone shop.example: minimum TLS version is 1.0

account acme-prod · zone shop.example · rule min_tls_below_1_2
category:securitysource:cloud_posturerisk:high
OverviewEvidenceFix stepsRest of the zone
Finding

Zone shop.example accepts TLS 1.0 at the edge. TLS 1.0 and 1.1 are deprecated and fail most compliance baselines (PCI DSS requires 1.2 or later for cardholder data).

Why this is an issue and not an MR

The setting lives in Cloudflare, not in a repository, and the assistant's Cloudflare access is read-only.

SurfaceReadValue
zone settings · min_tls_versionok1.0
zone settings · sslokstrict
security header · HSTSokdisabled (separate issue)

All three surfaces were read successfully in this run. An unreadable surface would be reported as unobserved, never as compliant.

  • Check the share of TLS 1.0/1.1 clients in edge analytics for the last 30 days. Old embedded clients are the usual blocker.
  • In Cloudflare: SSL/TLS → Edge Certificates → Minimum TLS Version → TLS 1.2.
  • The next daily scan re-reads the setting; a zone at TLS 1.2 or higher no longer produces this finding.
CheckResult
HSTSdisabled · medium · own issue
DNSSECdisabled · medium · own issue
Dangling CNAMEold-promo → NXDOMAIN · own issue
Managed WAF, rate limitingnot available on this plan · abstained

What keeps it honest.

Security reports lose their value the first time they are confidently wrong. These are the rules that prevent that.

No false "compliant"A control is satisfied only on positive evidence in the manifest. Silence and errors are unknown, and the report says it is not an attestation.
Unreadable is not cleanA denied or partial read makes a check abstain; if every read for an account is denied, you get one loud issue about that instead of an all-clear.
Deterministic rules, no modelEdge, forge, recommender and signature checks are plain code over API reads. The same state always gives the same findings.
Other people's words, verbatimGoogle's recommendation text is quoted, never rewritten. A Cloudflare finding that would echo a token, or anything token-shaped, is suppressed.
No double filingThe PDF creates no proposals, and recommender items the assistant already covers become evidence on the existing finding.
Read-only everywhereCloudflare, GitLab settings and GCP are only read. Changes are yours to make, guided by the issue.

Where this stands.

Live
  • Cloudflare edge posture: min TLS, HSTS, SSL mode, DNSSEC, WAF, rate limiting, dangling CNAMEs, egress allowlist
  • GitLab access, CI and runner review
  • GKE recommender ingestion, quoted verbatim, issue-only
  • Cosign-aware chart verify blocks and the digest receipt gate
In development
  • Control-mapped posture PDF (3 controls, on demand)
  • Draft MRs for GKE recommendations that map to a manifest in git
  • Resolving image digests from the registry at emission, instead of holding the pin
Roadmap
  • More controls: encryption at rest, audit policy, retention
  • Down-mapping to ISO 27001 and SOC 2
  • Container image signature verification on running workloads
  • Wider cloud posture beyond the GKE recommender
← Previous storyBackup health you can trust