{"id":19199146,"url":"https://github.com/canva-public/endpoint-vulnerability-management-samples","last_synced_at":"2026-01-31T02:32:40.788Z","repository":{"id":237828850,"uuid":"795366222","full_name":"canva-public/endpoint-vulnerability-management-samples","owner":"canva-public","description":"A companion repository to the Canva engineering blog post on how to do endpoint vulnerability management.","archived":false,"fork":false,"pushed_at":"2024-05-06T04:04:47.000Z","size":6,"stargazers_count":8,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-06-15T02:40:04.046Z","etag":null,"topics":["endpoint","security","sql","vulnerability"],"latest_commit_sha":null,"homepage":"","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/canva-public.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-05-03T06:01:53.000Z","updated_at":"2024-07-11T14:47:11.000Z","dependencies_parsed_at":"2024-05-03T09:24:52.609Z","dependency_job_id":"84bc6b36-0716-410b-9b31-4d420d75fa8c","html_url":"https://github.com/canva-public/endpoint-vulnerability-management-samples","commit_stats":null,"previous_names":["canva-public/endpoint-vulnerability-management-samples"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/canva-public/endpoint-vulnerability-management-samples","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/canva-public%2Fendpoint-vulnerability-management-samples","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/canva-public%2Fendpoint-vulnerability-management-samples/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/canva-public%2Fendpoint-vulnerability-management-samples/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/canva-public%2Fendpoint-vulnerability-management-samples/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/canva-public","download_url":"https://codeload.github.com/canva-public/endpoint-vulnerability-management-samples/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/canva-public%2Fendpoint-vulnerability-management-samples/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28927191,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-30T22:32:35.345Z","status":"online","status_checked_at":"2026-01-31T02:00:09.179Z","response_time":128,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["endpoint","security","sql","vulnerability"],"created_at":"2024-11-09T12:25:47.925Z","updated_at":"2026-01-31T02:32:40.774Z","avatar_url":"https://github.com/canva-public.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# Endpoint vulnerability management samples\n\nThis file is a companion to Canva's blog post on how we do [endpoint vulnerability management at scale](https://www.canva.dev/blog/engineering/endpoint-vulnerability-management-at-scale/). To avoid overloading readers with technical details, we've split the write-up. This lets more technical readers dive deep and learn how they can implement a similar solution for their organization.\n\nThe queries shown here support the `How we measure success` section of the blog and will follow the same pattern of headings.\n\n\u003e :warning: **Be aware that the following data is not real. The data is for illustrative purposes only and might showcase issues with the vulnerability management program. This is intentional, and we do it explicitly to demonstrate what issues can look like. The data doesn't reflect the actual data or status of the vulnerability management process at Canva.**\n\nThe sample data used in the graphs published on the blog is available in the [sample_data](/sample_data) folder.\n\n## How we measure success\n\nAfter ingesting the data into Snowflake, we can use the following query to see some basic information.\n\n```sql\nSELECT\n\tapplication,\n\tapplicationname,\n\tapplicationvendor,\n\tapplicationversion,\n\tcveid,\n\tdetectiondate,\n\tendpoint_data_id,\n\tid,\n\tlastscandate,\n\tlastscanresult,\n\tnvdbasescore,\n\tnvdcvssversion,\n\tostype,\n\tpublisheddate,\n\tingestion_date\nFROM\n\tvulnerability\n```\n\nThe integration that reads the data from S3 and ingests it into Snowflake adds the `ingestion_date` field, allowing us to see a snapshot of vulnerability data at any previous point in time and review trends over time.\n\n### Vulnerabilities over time\n\nThis view shows us whether our methods to reduce vulnerabilities work.\n\n\u003e :note: **The following query, as well as other queries, use a statement to limit the data returned by the queries to the last 90 days. If you're experimenting with the sample data, you might need to adjust this value accordingly.**\n\n\u003e :note: **Be aware that the average used here is relative to the previous rows returned by the query. Days where no data is returned might skew the results. You can address the issue, but you need to use a [CTE](https://docs.snowflake.com/en/user-guide/queries-cte) containing all the dates and then perform a `JOIN` operation against the returned data. Because of the added complexity, this work is considered out of scope for this document.**\n\n```sql\nSELECT\n    DATE_TRUNC('DAY', ingestion_date) AS \"Date\",\n    -- Sets the severity of the vulnerability based on the score.\n    CASE\n        WHEN nvdbasescore \u003c 4 THEN 'Low'\n        WHEN nvdbasescore \u003c 7 THEN 'Medium'\n        WHEN nvdbasescore \u003c 9 THEN 'High'\n        WHEN nvdbasescore \u003c= 10 THEN 'Critical'\n        ELSE 'Unknown'\n    END AS \"CVSS Severity\",\n    -- Each vulnerability ID is a different record, we want to count those.\n    COUNT(DISTINCT id) as \"Vulnerability Count\",\n    -- This average is useful to see the general trend for the data.\n    AVG(\"Vulnerability Count\") OVER (\n        PARTITION BY \"CVSS Severity\" ORDER BY \"Date\" ROWS BETWEEN 2 PRECEDING AND CURRENT ROW\n    ) AS \"3 day average\"\nFROM\n    vulnerability\nWHERE\n    -- Only show data for the past 90 days.\n    DATE_TRUNC('DAY', ingestion_date) \u003e CURRENT_DATE - INTERVAL '90 days'\nGROUP BY \"Date\", \"CVSS Severity\"\nORDER BY \"Date\"\n```\n\n### Top widespread vulnerable applications\n\nWith this view, you can visualize if a set of applications, in particular, introduces the most risk to the organization. It can help prioritize efforts into which applications to manage.\n\n```sql\nSELECT TOP 10\n\t-- We're not using vendor in the graph, but would be just as easy to see vendor data if we were interested in it.\n\tapplicationvendor,\n\tapplicationname,\n\t-- This will give us the number of endpoints where the vulnerable application is deployed.\n\tCOUNT(DISTINCT endpoint_data_id) as \"Vulnerable Endpoints\"\nFROM\n\tvulnerability\nWHERE\n\t-- For this query, we're only interested in the data from the latest report.\n\tingestion_date = (SELECT MAX(ingestion_date) FROM vulnerability)\nGROUP BY\n\tapplicationvendor,\n\tapplicationname\nORDER BY\n\t\"Vulnerable Endpoints\"\nDESC\n```\n\n### Vulnerability age through time\n\nThis view provides interesting data about the vulnerability age in the environment. The graph tends to climb steadily because each day, vulnerabilities become a day older, but it can give you some insights into how old the unaddressed vulnerabilities are.\n\n```sql\nSELECT\n\t-- Calculate percentiles using days since the vulnerability was detected.\n\tPERCENTILE_DISC(0.5) WITHIN GROUP(ORDER BY daysdetected) AS p50,\n\tPERCENTILE_DISC(0.95) WITHIN GROUP(ORDER BY daysdetected) AS p95,\n\tMAX(daysdetected::INTEGER) AS \"Max\",\n\tDATE_TRUNC('DAY', ingestion_date) AS \"Date\"\nFROM\n\tvulnerability\nWHERE\n\t-- Only show data for the past 90 days.\n\tDATE_TRUNC('DAY', ingestion_date) \u003e DATE_TRUNC('DAY', CURRENT_DATE - INTERVAL '90 days')\nGROUP BY\n\t-- Group by date so we can see how the numbers change daily and look at trends over time.\n\t\"Date\"\nORDER BY\n\t\"Date\"\n```\n\n### Widespread managed vulnerable applications out of SLA\n\nThe view produced by the following query is what we've found most impactful at Canva. It’s more complex than previous views, using an external table to determine which applications we centrally manage. Because non-managed applications are excluded from results, it should, in theory, return no data. Realistically, however, we expect spikes from vulnerabilities being published and then resolved automatically throughout the fleet. Any sustained numbers indicate issues with the vulnerability management process.\n\n```sql\nWITH vulnerabilities_out_of_sla_managed_apps AS (\n    SELECT\n        applicationname,\n        applicationvendor,\n        applicationversion,\n        daysdetected,\n        nvdbasescore,\n        endpoint_data_id,\n        ingestion_date\n    FROM\n        vulnerability\n    JOIN\n        /* We are joining the vulnerability data with a table that contains managed data, this\n        /* allows us to discard vulnerable applications that are not centrally managed. */\n        managed_applications\n    ON\n        LOWER(applicationname) = LOWER(managed_applications.application_name)\n        AND\n        (\n            LOWER(applicationvendor) = LOWER(application_vendor)\n            OR managed_applications.application_vendor IS NULL\n        )\n        AND LOWER(ostype) = LOWER(managed_applications.application_os)\n    WHERE\n        -- Only get data for the past 90 days.\n        DATE_TRUNC('DAY', ingestion_date) \u003e CURRENT_DATE - INTERVAL '90 days'\n        -- The logic below checks whether a vulnerability is breaching SLA.\n        AND\n        (\n            (\n                -- For low vulnerabilities, the SLA is 10 days.\n                nvdbasescore \u003e= 0 AND nvdbasescore \u003c 4 AND daysdetected \u003e= 10\n            )\n            OR\n            (\n                -- For medium vulnerabilities, the SLA is 3 days.\n                nvdbasescore \u003e= 4 AND nvdbasescore \u003c 7 AND daysdetected \u003e= 3\n            )\n            OR\n            (\n                -- For high vulnerabilities, the SLA is 2 days.\n                (\n                    (\n                        nvdbasescore \u003e= 7 AND nvdbasescore \u003c 9\n                    )\n                    /* Sometimes the CVSS score is not known. Out of caution we want to treat\n                    /* vulnerabilities with unknown CVSS scores as high. */\n                    OR nvdbasescore IS NULL\n                )\n                AND daysdetected \u003e= 2\n            )\n            OR\n            (\n                -- For critical vulnerabilities, the SLA is 1 day.\n                nvdbasescore \u003e= 9 AND nvdbasescore \u003c= 10 AND daysdetected \u003e= 1\n            )\n        )\n/* This CTE takes the previous one as input and ouputs one record for every vulnerable application\n/* per endpoint per day. So for instance instead of having 50 rows referencing software x on date y\n/* and endpoint z it will show a single record referencing software x, date y and endpoint z. */\n), per_app AS (\n    SELECT\n        DISTINCT applicationname,\n        endpoint_data_id,\n        /* Different vulnerabilities could be affecting the same application, we're only interested\n        /* in knowing the highest severity and oldest vulnerability. While the maximum values don't\n        /* necessarily always belong to the same vulnerability, it is the case most of the time so\n        /* it is good enough for us not to have to make the distinction. */\n        MAX(nvdbasescore) AS max_severity_score,\n        MAX(daysdetected) AS vulnerability_age,\n        ingestion_date\n    FROM\n        vulnerabilities_out_of_sla_managed_apps\n    group by\n        applicationname,\n        endpoint_data_id,\n        ingestion_date\n/* This CTE is the same as the previous one but adds a new column where it translates the CVSS\n/* scores to human readable labels which will make it easier to graph. */\n), per_app_with_severity_label AS (\n    SELECT\n        *,\n        CASE\n            WHEN max_severity_score \u003e= 0 AND max_severity_score \u003c 4 THEN 'Low'\n            WHEN max_severity_score \u003e= 4 AND max_severity_score \u003c 7 THEN 'Medium'\n            WHEN max_severity_score \u003e= 7 AND max_severity_score \u003c 9 THEN 'High'\n            WHEN max_severity_score \u003e= 9 AND max_severity_score \u003c= 10 THEN 'Critical'\n            ELSE 'Unknown'\n        END AS \"CVSS Severity\"\n    FROM\n        per_app\n)\nSELECT\n    -- This will give us the number of vulnerable applications per endpoint.\n    COUNT(DISTINCT applicationname, endpoint_data_id),\n    \"CVSS Severity\",\n    ingestion_date\nFROM\n    per_app_with_severity_label\nGROUP BY\n    \"CVSS Severity\",\n    ingestion_date\nORDER BY\n    ingestion_date desc,\n    \"CVSS Severity\"\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcanva-public%2Fendpoint-vulnerability-management-samples","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcanva-public%2Fendpoint-vulnerability-management-samples","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcanva-public%2Fendpoint-vulnerability-management-samples/lists"}