Findings are cheap.
A promoted issue is accountable.

That's the thesis behind the evidence ledger for cloud security — self-hosted CSPM (cloud security posture management) that runs on your infrastructure. 1,890 checks, every one readable SQL, recording what each finding became — proven, decided, fixed, verified — in a database you own.

Your teams ship at machine speed. Unscanned weeks are unrecorded risk.

Credentials come from a secrets manager — never stored in code or logs. Verified against your provider before anything is fetched.

you@your-vm:~$ kloudle ledger show ISSUE-████

Sample — redacted example,
not a customer record
ledger entry · ISSUE-████
S3 bucket Public Access Block is disabled
check: KSP10010 · S3.PublicAccessBlock · provider: AWS · S3
resource: s3://█████-assets-prod · account: ████████
severity: HIGH · frameworks: CIS 2.1.4
  1. 2026-07-28
    09:14:03Z
    01Observed Live today

    kloudle-aws-s3 reports public-access-block disabled. One line among hundreds returned by the scan — not yet a security issue.

  2. 2026-07-28
    09:14:41Z
    02Proven Live today

    evidence: bucket-policy.json acl.json public-access-block.json (snapshot at scan time)

  3. 2026-07-28
    09:31:19Z
    03Promoted Near-term

    severity: High · why: bucket reachable via public CloudFront distribution; objects written by a deploy role an agent uses

  4. 2026-07-28
    09:31:20Z
    04Recorded Near-term

    linked: KSP10179 (CloudFront origin → public-readable S3), deploy-role identity node · written to ledger

  5. 2026-07-29
    15:02:56Z
    05Human decision Near-term

    gate: allow-scoped-fix · agent scope: S3.PublicAccessBlock on this resource only · waiver alternative: 30d with reason

  6. 2026-07-29
    15:04:11Z
    06Acted from ledger Destination

    remediation: enable public-access-block · scope: this resource only · change-proof returned to ledger

  7. 2026-07-30
    03:00:37Z
    07Verified Near-term

    re-scan: S3.PublicAccessBlock → pass · status: resolved, verified

status: resolved · verified PASS append-only · recorded in your database

Every figure above is a number you can audit further down this page.

Severity maps to CIS, NIST, and PCI-DSS benchmarks — not to a vendor score.

A dashboard is where findings go to age.

Four thousand alerts is not a security posture. It's a backlog with a color scheme.

Every CSPM can flood you with findings. None of them can tell you what happened next. What was proven. What was waived, by whom, with what expiry. What an agent was allowed to touch.

That history is the part an auditor asks for. It's the part a dashboard doesn't keep. Worse: in most tools, the record of your own risk lives in someone else's cloud, on someone else's retention policy.

A finding answers nothing. A record answers everything: what you saw, what you proved, what you decided, what you fixed, who signed off.

The ledger is the durable record of what you observed, what you proved, who decided, and what happened next.

any CSPM · findings feed4,000+ opensorted: newest
CRITS3 bucket is public to the internet · s3://█████████open · 201d
HIGHManagement ports (SSH/RDP) exposed to the world · sg-████████snoozed · 154d
HIGHS3 bucket Public Access Block is disabled · s3://█████████duplicate · 61d
MEDCloudFront origin points at public-readable S3 · ████████open · 118d
CRITGenAI agent running without a jailbreak guardrail · ████████unassigned · 97d
MEDS3 bucket is public to the internet · s3://█████████duplicate · 84d
HIGHManagement ports (SSH/RDP) exposed to the world · sg-████████snoozed · 90d
MEDCloudFront origin points at public-readable S3 · ████████open · 66d
HIGHS3 bucket Public Access Block is disabled · s3://█████████duplicate · 44d
CRITS3 bucket is public to the internet · s3://█████████open · 38d
HIGHGenAI agent running without a jailbreak guardrail · ████████unassigned · 22d
MEDManagement ports (SSH/RDP) exposed to the world · sg-████████open · 12d
what happened next: not recorded
Sample — redacted example,
not a customer record
ledger entry · ISSUE-████
Management Ports (SSH/RDP) exposed to the world in Security Groups
check: KSP10139 · provider: AWS · EC2
resource: sg-████████ (█████-staging-vpc) · account: ████████
  1. 2026-08-01
    02:00:12Z
    01Observed Live today

    kloudle-aws-ec2 reports 0.0.0.0/0 ingress on tcp/22.

  2. 2026-08-01
    02:00:58Z
    02Proven Live today

    evidence: security-group-rules.json (snapshot) — the rule itself, captured at scan time, stored in your database with pass/fail history.

status: awaiting promotion · next stage: cross-plane context Near-term

Exhibit A — the dashboard (simulated) · severity, age, silence

Exhibit B — the ledger entry · what was seen, what was proved, and whose it is

Evidence-backed findings run today. Every lifecycle stage on this page carries its badge: live, near-term, destination.

See the unit →

Seven stages. One record that answers for itself.

Append-only, nothing overwritten — the entry is the audit trail.

Every issue in the ledger carries its whole life. Each stage row shows its name, status label, and maturity badge; the one-line description sits behind a per-stage expand.

Humans set the gates. Agents work the list. Every stage transition is appended with a timestamp. A waived issue is still an accountable one.

Two of these stages run today. Four are being built on the shipping engine. One is the destination. The badges are on the page because you'd ask anyway.

Sample — redacted example,
not a customer record
ledger entry · ISSUE-████
S3 bucket Public Access Block is disabled
check: KSP10010 · S3.PublicAccessBlock · provider: AWS · S3
resource: s3://█████-assets-prod · account: ████████
severity: HIGH · frameworks: CIS 2.1.4
  1. 2026-07-28
    09:14:03Z
    01Observed Live today

    scanner output; one line among hundreds; not yet a security issue.

    kloudle-aws-s3 reports public-access-block disabled. One line among hundreds returned by the scan — not yet a security issue.

  2. 2026-07-28
    09:14:41Z
    02Proven Live today

    raw config captured at scan time: files, not scores.

    evidence: bucket-policy.json acl.json public-access-block.json (snapshot at scan time)

  3. 2026-07-28
    09:31:19Z
    03Promoted Near-term

    ranked with context from across your cloud: what the resource touches, why it matters.

    severity: High · why: bucket reachable via public CloudFront distribution; objects written by a deploy role an agent uses

  4. 2026-07-28
    09:31:20Z
    04Recorded Near-term

    linked to related risk; written to the ledger you own.

    linked: KSP10179 (CloudFront origin → public-readable S3), deploy-role identity node · written to ledger

  5. 2026-07-29
    15:02:56Z
    05Human decision Near-term

    block, allow-scoped-fix, or waive with reason and expiry; agent scope set here.

    gate: allow-scoped-fix · agent scope: S3.PublicAccessBlock on this resource only · waiver alternative: 30d with reason

  6. 2026-07-29
    15:04:11Z
    06Acted from ledger Destination

    scoped remediation with change-proof returned.

    remediation: enable public-access-block · scope: this resource only · change-proof returned to ledger

  7. 2026-07-30
    03:00:37Z
    07Verified Near-term

    re-scan result recorded against the entry.

    re-scan: S3.PublicAccessBlock → pass · status: resolved, verified

status: resolved · verified PASS append-only · recorded in your database
Sample — redacted example,
not a customer record
ledger entry · ISSUE-████
S3 bucket is public to the Internet
check: KSP10011 · provider: AWS · S3
resource: s3://█████-www-static · account: ████████
severity: MEDIUM
  1. 2026-07-30
    02:00:09Z
    01Observed Live today

    scanner output; one line among hundreds; not yet a security issue.

    kloudle-aws-s3 reports bucket public to the Internet.

  2. 2026-07-30
    02:00:47Z
    02Proven Live today

    raw config captured at scan time: files, not scores.

    evidence: bucket-policy.json acl.json (snapshot)

  3. 2026-07-30
    02:17:33Z
    03Promoted Near-term

    ranked with context from across your cloud: what the resource touches, why it matters.

    severity: Medium · why: public reachability, no sensitive-writer identities in graph

  4. 2026-07-31
    10:42:18Z
    05Human decision Near-term

    block, allow-scoped-fix, or waive with reason and expiry; agent scope set here.

    gate: waive · reason: intentional public static-site bucket · expiry: 90d, re-review on expiry · agent scope: none

status: waived (expires ████-██-██) WAIVED
the waiver, its reason, and its author are part of the durable record — a waived issue is still an accountable one

1,890 checks. Here's one. Read it.

AWS 681. GCP 338. Kubernetes 306. Azure 292. DigitalOcean 273. Every check is a SQL query against your own resource snapshots — no black-box detections. This is one of them, verbatim.

Don't take the number's word for it. Below is KSP10011 — "S3 bucket is public to the internet," severity Critical — exactly as it ships. It joins your bucket's ACL grants, bucket policy, and public-access-block config, and returns pass or fail with the reason in plain text. Disagree with a result? The query is the argument.

Severity maps to CIS, NIST, and PCI-DSS benchmarks — not to a vendor score. And the catalog covers the cloud you actually have, including the AI in it: KSP10139, SSH and RDP open to the world in a security group. KSP11165, a DigitalOcean GenAI agent running without a jailbreak guardrail.

checks/aws-s3/bucket-public-composite.sql
S3 bucket is public to the internet
check: KSP10011 · severity: CRITICAL · resource: s3_bucket · mapping: checks/ksp_mapping.json
-- checks/aws-s3/bucket-public-composite.sql
-- Check: KSP10011
-- Severity: CRITICAL
-- Description: S3 buckets should not be publicly accessible (composite: ACL + policy + public access block)

SELECT
    b.arn as resource_id,
    SPLIT_PART(b.arn, ':::', 2) as resource_name,
    CASE
        -- Pass if all public access is blocked
        WHEN pab.public_access_block_configuration IS NOT NULL
             AND jdecode(pab.public_access_block_configuration)->>'BlockPublicAcls' = 'true'
             AND jdecode(pab.public_access_block_configuration)->>'BlockPublicPolicy' = 'true'
             AND jdecode(pab.public_access_block_configuration)->>'IgnorePublicAcls' = 'true'
             AND jdecode(pab.public_access_block_configuration)->>'RestrictPublicBuckets' = 'true' THEN 'pass'
        -- Fail if ACL grants to AllUsers
        WHEN acl_public.bucket_arn IS NOT NULL THEN 'fail'
        -- Fail if policy allows public access (wildcard principal in an Allow statement)
        WHEN pol.policy IS NOT NULL AND EXISTS (
            SELECT 1 FROM jsonb_array_elements(
                CASE jsonb_typeof(jdecode(pol.policy)->'Statement')
                    WHEN 'array' THEN jdecode(pol.policy)->'Statement'
                    ELSE jsonb_build_array(jdecode(pol.policy)->'Statement')
                END
            ) AS stmt
            WHERE stmt->>'Effect' = 'Allow'
              AND (stmt->>'Principal' = '*' OR stmt->'Principal'->'AWS' @> '"*"'::jsonb)
        ) THEN 'fail'
        ELSE 'pass'
    END as status,
    CASE
        WHEN pab.public_access_block_configuration IS NOT NULL
             AND jdecode(pab.public_access_block_configuration)->>'BlockPublicAcls' = 'true'
             AND jdecode(pab.public_access_block_configuration)->>'BlockPublicPolicy' = 'true'
             AND jdecode(pab.public_access_block_configuration)->>'IgnorePublicAcls' = 'true'
             AND jdecode(pab.public_access_block_configuration)->>'RestrictPublicBuckets' = 'true'
            THEN 'All public access is blocked via public access block configuration'
        WHEN acl_public.bucket_arn IS NOT NULL
            THEN 'Bucket ACL grants access to AllUsers (internet) - enable public access block or remove AllUsers grants'
        WHEN pol.policy IS NOT NULL AND EXISTS (
            SELECT 1 FROM jsonb_array_elements(
                CASE jsonb_typeof(jdecode(pol.policy)->'Statement')
                    WHEN 'array' THEN jdecode(pol.policy)->'Statement'
                    ELSE jsonb_build_array(jdecode(pol.policy)->'Statement')
                END
            ) AS stmt
            WHERE stmt->>'Effect' = 'Allow'
              AND (stmt->>'Principal' = '*' OR stmt->'Principal'->'AWS' @> '"*"'::jsonb)
        )
            THEN 'Bucket policy allows public access (Principal: *) - restrict the policy or enable public access block'
        ELSE 'Bucket is not publicly accessible'
    END as reason
FROM aws_s3_buckets b
LEFT JOIN aws_s3_bucket_public_access_blocks pab
    ON b.arn = pab.bucket_arn
LEFT JOIN (
    SELECT DISTINCT bucket_arn
    FROM aws_s3_bucket_grants
    WHERE grantee_type = 'Group'
      AND jdecode(grantee)->>'URI' = 'http://acs.amazonaws.com/groups/global/AllUsers'
) acl_public ON b.arn = acl_public.bucket_arn
LEFT JOIN aws_s3_bucket_policies pol
    ON b.arn = pol.bucket_arn;

Collapsed to the first ~14 lines — the full query is a desktop read.

63 lines · rendered verbatim from the catalog

jdecode() is a Kloudle engine UDF — it decodes snapshot JSON columns to jsonb. Ships with the catalog in database/functions.sql.

Snapshot first. Verdict second.

On demand or on schedule. Credentials verified against the live provider before a single resource is fetched.

A scan starts by proving it should. Credentials are checked against the provider itself before anything is fetched. They come from a secrets manager — never code, never logs.

Then discovery: your assets enumerated and stored as raw snapshots. Every expected snapshot is confirmed written before evaluation begins. Only then does the full catalog run.

Every stage emits an event and the UI updates as it happens — an append-only timeline of scan events, pushed live, no polling. Out the other end: PDF and JSON reports — executive summary, detailed findings, asset inventory, compliance, audit — with templates for SOC2, ISO27001, HIPAA, and PCI-DSS.

This is snapshot-based scanning, on demand or scheduled — you set the cadence, and you can see from the timeline exactly when each verdict was taken.

What lands in your database →
scan · 2026-08-01 · account ████████pushed live · no polling
  1. 02:00:00Z
    scan started

    on schedule · snapshot runner

  2. 02:00:03Z
    credentials verified

    against the live provider · pulled from your secrets manager

  3. 02:00:05Z
    creating tasks

    split into parallel per-service tasks

  4. 02:00:09Z
    tasks created
  5. 02:00:10Z
    scanning

    assets enumerated · raw snapshots stored

  6. 02:07:41Z
    scan completed

    every expected snapshot confirmed written

  7. 02:07:43Z
    evaluating

    full catalog against the snapshot

  8. 02:09:58Z
    generating reports
  9. 02:10:46Z
    report generated

    PDF · JSON ↓

out the other end · PDF + JSON
executive-summary.pdf detailed-findings.pdf asset-inventory.json compliance.pdf audit.pdf
templates: SOC2ISO27001HIPAAPCI-DSS

The nine status strings written to the scan record — the proof bar's “9 real scan states”, receipted

Your infrastructure. Your database. Your record.

“Wiz and Datadog make money by centralizing your security data in their cloud. Kloudle makes money by keeping it in yours.”

You host every part of it. The scanner runs on your infrastructure. Every snapshot, every finding, and every future ledger entry lands in a database you administer.

When the results live in your database, retention is your policy and access is your call. Leave us, and you keep the record.

Your perimeter
Kloudle scanner
runs on your infrastructure · snapshot-based · on demand or scheduled
Your database
snapshots · findings · pass/fail history · every future ledger entry

Nothing crosses out. retention: your policy · access: your call

Your perimeter · a SaaS CSPM
Your cloud
your security data, collected —
Vendor's cloud
their database · their retention policy · their access

Self-hosted — the record stays in

Centralized SaaS — the record ships out

Book a demo — see it land in your database

Credentials come from a secrets manager — never stored in code or logs.

Humans set the gates. Agents work the list.

An MCP server (Model Context Protocol — the interface AI agents use to call tools), a CLI, and binaries ship today. Your agents read the same list your engineers do.

Your team ships at machine speed. Your misconfigurations arrive at the same speed. This is posture for that software factory. The same 1,890 checks are reachable by your engineers in the UI and by your agents over MCP, CLI, and binaries — today.

The destination is further: agents acting from the promoted ledger — scoped to one resource, one fix, with change-proof returned to the record. Never from raw scanner noise. That stage carries its Destination badge: it isn't shipping yet, and the badge means you never have to guess.

Agents work the list over MCP server CLI binaries Live today
How the gate works →
ledger entry · ISSUE-████ · stages 05–06 · excerpt
05Human decision Near-term

gate: allow-scoped-fix · agent scope: this resource only

waiver alternative: 30d with reason · appended with a timestamp, part of the durable record

06Acted from ledger Destination

remediation: enable public-access-block · scope: this resource only · change-proof returned to ledger

acts from the promoted ledger · never from raw scanner noisehumans stay responsible

Five surfaces. One record.

Each labeled with what it is — not what you're being sold.

One is live and scanning accounts today. The rest are labeled ambitions — built on the same engine, feeding the same record.

Audit us before you buy us.

You already read the SQL. The rest of the demo works the same way.

No logos. No testimonials. What you get instead, sitting where the badge wall usually goes, is evidence you can check yourself: 1,890 checks — one of them printed in full on this page — real check IDs, real scan states, and a sample ledger entry that tells you it's a sample.

A demo walks a real scan's output: evidence files, PDF and JSON reports, and the ledger those results feed — badged stage by stage, so you know exactly what's live and what's next.

AI-assisted teams ship misconfigurations at machine speed. Every unscanned week is unrecorded risk.

Self-hosted. Credentials verified before anything is fetched — from a secrets manager, never code or logs.

07 Verified · re-scan: pass · status: resolved, verified