Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ Every file answers one question: **which controls from framework X address vulne
| **70+** open-source tools | Catalogued and organised by function |
| **25** eval profiles | Runnable Garak (13) + PyRIT (6) + LAAF (6) tests mapped to OWASP entries |
| **<!-- stats:frameworks-mapped -->26<!-- /stats -->** compliance reports | Per-framework gap assessments auto-generated from data layer (MD, CSV, JSON, OSCAL) |
| **<!-- stats:incidents -->134<!-- /stats -->** documented incidents | Real-world + research incidents with MAESTRO layer attribution (MD, CSV, JSON, STIX 2.1) |
| **<!-- stats:incidents -->135<!-- /stats -->** documented incidents | Real-world + research incidents with MAESTRO layer attribution (MD, CSV, JSON, STIX 2.1) |
| **LAAF v2.0** | First agentic LPCI red-teaming framework — fully integrated with 6-stage × OWASP crosswalk |

All free. All open-source. Built for practitioners.
Expand Down
6 changes: 6 additions & 0 deletions data/entries/DSGAI01.json
Original file line number Diff line number Diff line change
Expand Up @@ -1174,6 +1174,12 @@
"url": "https://github.com/GenAI-Security-Project/crosswalk/blob/main/data/incidents.json",
"year": 2026,
"incident_id": "INC-112"
},
{
"name": "llmware: SQL injection in the collection-database layer exposed documents the caller was scoped out of",
"url": "https://github.com/GenAI-Security-Project/crosswalk/blob/main/data/incidents.json",
"year": 2026,
"incident_id": "INC-135"
}
],
"crossrefs": {
Expand Down
6 changes: 6 additions & 0 deletions data/entries/DSGAI13.json
Original file line number Diff line number Diff line change
Expand Up @@ -985,6 +985,12 @@
"url": "https://github.com/GenAI-Security-Project/crosswalk/blob/main/data/incidents.json",
"year": 2023,
"incident_id": "INC-077"
},
{
"name": "llmware: SQL injection in the collection-database layer exposed documents the caller was scoped out of",
"url": "https://github.com/GenAI-Security-Project/crosswalk/blob/main/data/incidents.json",
"year": 2026,
"incident_id": "INC-135"
}
],
"crossrefs": {
Expand Down
16 changes: 16 additions & 0 deletions data/incidents-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,22 @@
"raw_description": {
"type": "string",
"description": "The original pasted text, retained until a human signs off on the cleaned `description`. Never deleted by tooling."
},
"incident_class": {
"type": "string",
"enum": [
"tooling-cve",
"ai-behaviour"
],
"description": "What kind of failure this is. `tooling-cve` is a conventional software vulnerability in GenAI tooling — SQL injection, missing authorisation, CSWSH — that is AI-relevant because of what it exposes, not because the model behaved badly. `ai-behaviour` is a failure of the AI system's own behaviour: prompt injection, poisoning, agent goal deviation. Records without the field predate it."
},
"mapping_status": {
"type": "string",
"enum": [
"draft",
"sme-confirmed"
],
"description": "Whether `owasp_entries` has been confirmed by a reviewer. `draft` means an agent proposed the entries from the source text and no SME has signed them off; it is not a claim about the incident, only about who decided the mapping."
}
}
}
Expand Down
86 changes: 84 additions & 2 deletions data/incidents.json
Original file line number Diff line number Diff line change
Expand Up @@ -8258,7 +8258,9 @@
"source_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-37003",
"confirmed_by": []
}
]
],
"incident_class": "tooling-cve",
"mapping_status": "draft"
},
{
"id": "INC-133",
Expand Down Expand Up @@ -8337,7 +8339,9 @@
"source_url": "https://github.com/samanhappy/mcphub/security/advisories/GHSA-6cvf-cfch-4g7m",
"confirmed_by": []
}
]
],
"incident_class": "tooling-cve",
"mapping_status": "draft"
},
{
"id": "INC-134",
Expand Down Expand Up @@ -8415,6 +8419,84 @@
"source_url": "https://github.com/headroomlabs-ai/headroom/security/advisories/GHSA-h46j-26q3-rggf",
"confirmed_by": []
}
],
"incident_class": "tooling-cve",
"mapping_status": "draft"
},
{
"id": "INC-135",
"title": "llmware: SQL injection in the collection-database layer exposed documents the caller was scoped out of",
"date": "2026-08",
"year": 2026,
"category": "research-demonstrated",
"incident_class": "tooling-cve",
"severity": "High",
"description": "CVE-2026-85689 reports that llmware 0.4.6 builds SQL WHERE clauses by string-interpolating filter and lookup values directly, \"without parameterization or escaping, in both the SQLite and PostgreSQL backends\". The CNA record states that \"the filter validator only checks keys against an allow-list and never sanitizes values\", and that attacker-controlled filter values reaching the public API through Library.block_lookup and Query.text_query_with_custom_filter / text_query_by_author_or_speaker \"can neutralize the intended filter to disclose rows the caller was scoped out of (cross-document/cross-collection disclosure); on PostgreSQL the flaw permits boolean- and UNION-based SQL injection.\" Provenance and status, stated plainly: the CNA is VulnCheck rather than the vendor, the CVSS 7.1 is VulnCheck's own secondary metric, llmware has published no security advisory, the upstream report (llmware-ai/llmware#1304, opened 2026-06-12) remains open with no maintainer response, and no release after the affected 0.4.6 exists — so this is recorded as unfixed at the time of writing.",
"owasp_entries": [
"DSGAI13",
"DSGAI01"
],
"mapping_status": "draft",
"maestro_layers": [
{
"layer": "L2",
"label": "Data Operations",
"role": "origin",
"notes": "The collection-database layer holds the document and metadata store a RAG pipeline queries"
},
{
"layer": "L6",
"label": "Security & Compliance",
"role": "impact",
"notes": "The per-caller scoping the filter was meant to enforce is neutralised by the injected value"
}
],
"attack_vector": "Attacker-controlled filter or lookup value reaching Library.block_lookup or Query.text_query_* and interpolated into a SQL WHERE clause",
"affected": "llmware 0.4.6 (latest release at the time of writing; no fixed version published)",
"impact": "Cross-document and cross-collection disclosure of rows the caller was scoped out of; boolean- and UNION-based SQL injection on the PostgreSQL backend",
"mitigations": [
"Parameterise every filter and lookup value rather than interpolating it into SQL",
"Validate filter values, not only their keys — the allow-list covers keys alone",
"Scope database credentials per tenant so a neutralised filter cannot reach other collections"
],
"external_refs": [
{
"source": "CVE",
"id": "CVE-2026-85689",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-85689"
},
{
"source": "research",
"id": "llmware-ai/llmware#1304",
"url": "https://github.com/llmware-ai/llmware/issues/1304"
}
],
"references": [
{
"title": "NVD — CVE-2026-85689",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-85689",
"type": "advisory"
},
{
"title": "VulnCheck advisory — llmware 0.4.6 SQL injection via unescaped filter values",
"url": "https://www.vulncheck.com/advisories/llmware-0.4.6-sql-injection-via-unescaped-filter-values",
"type": "advisory"
},
{
"title": "Upstream report llmware-ai/llmware#1304 (open, unfixed)",
"url": "https://github.com/llmware-ai/llmware/issues/1304",
"type": "disclosure"
}
],
"tags": [
"cve",
"tooling-cve",
"sql-injection",
"rag",
"llmware",
"cross-tenant-disclosure",
"unfixed",
"draft-mapping"
]
}
]
Expand Down
2 changes: 1 addition & 1 deletion data/stats.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
}
},
"incidents": {
"total": 134
"total": 135
},
"evidence": {
"incidents_annotated": 16,
Expand Down
12 changes: 12 additions & 0 deletions docs/data.js
Original file line number Diff line number Diff line change
Expand Up @@ -26211,6 +26211,12 @@ window.CROSSWALK_DATA = [
"url": "https://github.com/GenAI-Security-Project/crosswalk/blob/main/data/incidents.json",
"year": 2026,
"incident_id": "INC-112"
},
{
"name": "llmware: SQL injection in the collection-database layer exposed documents the caller was scoped out of",
"url": "https://github.com/GenAI-Security-Project/crosswalk/blob/main/data/incidents.json",
"year": 2026,
"incident_id": "INC-135"
}
],
"crossrefs": {
Expand Down Expand Up @@ -38319,6 +38325,12 @@ window.CROSSWALK_DATA = [
"url": "https://github.com/GenAI-Security-Project/crosswalk/blob/main/data/incidents.json",
"year": 2023,
"incident_id": "INC-077"
},
{
"name": "llmware: SQL injection in the collection-database layer exposed documents the caller was scoped out of",
"url": "https://github.com/GenAI-Security-Project/crosswalk/blob/main/data/incidents.json",
"year": 2026,
"incident_id": "INC-135"
}
],
"crossrefs": {
Expand Down
88 changes: 85 additions & 3 deletions docs/incidents.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Auto-generated by scripts/generate.js — do not edit manually
// Incidents: 134
// Incidents: 135
window.CROSSWALK_INCIDENTS = [
{
"id": "INC-001",
Expand Down Expand Up @@ -8256,7 +8256,9 @@ window.CROSSWALK_INCIDENTS = [
"source_url": "https://nvd.nist.gov/vuln/detail/CVE-2026-37003",
"confirmed_by": []
}
]
],
"incident_class": "tooling-cve",
"mapping_status": "draft"
},
{
"id": "INC-133",
Expand Down Expand Up @@ -8335,7 +8337,9 @@ window.CROSSWALK_INCIDENTS = [
"source_url": "https://github.com/samanhappy/mcphub/security/advisories/GHSA-6cvf-cfch-4g7m",
"confirmed_by": []
}
]
],
"incident_class": "tooling-cve",
"mapping_status": "draft"
},
{
"id": "INC-134",
Expand Down Expand Up @@ -8413,6 +8417,84 @@ window.CROSSWALK_INCIDENTS = [
"source_url": "https://github.com/headroomlabs-ai/headroom/security/advisories/GHSA-h46j-26q3-rggf",
"confirmed_by": []
}
],
"incident_class": "tooling-cve",
"mapping_status": "draft"
},
{
"id": "INC-135",
"title": "llmware: SQL injection in the collection-database layer exposed documents the caller was scoped out of",
"date": "2026-08",
"year": 2026,
"category": "research-demonstrated",
"incident_class": "tooling-cve",
"severity": "High",
"description": "CVE-2026-85689 reports that llmware 0.4.6 builds SQL WHERE clauses by string-interpolating filter and lookup values directly, \"without parameterization or escaping, in both the SQLite and PostgreSQL backends\". The CNA record states that \"the filter validator only checks keys against an allow-list and never sanitizes values\", and that attacker-controlled filter values reaching the public API through Library.block_lookup and Query.text_query_with_custom_filter / text_query_by_author_or_speaker \"can neutralize the intended filter to disclose rows the caller was scoped out of (cross-document/cross-collection disclosure); on PostgreSQL the flaw permits boolean- and UNION-based SQL injection.\" Provenance and status, stated plainly: the CNA is VulnCheck rather than the vendor, the CVSS 7.1 is VulnCheck's own secondary metric, llmware has published no security advisory, the upstream report (llmware-ai/llmware#1304, opened 2026-06-12) remains open with no maintainer response, and no release after the affected 0.4.6 exists — so this is recorded as unfixed at the time of writing.",
"owasp_entries": [
"DSGAI13",
"DSGAI01"
],
"mapping_status": "draft",
"maestro_layers": [
{
"layer": "L2",
"label": "Data Operations",
"role": "origin",
"notes": "The collection-database layer holds the document and metadata store a RAG pipeline queries"
},
{
"layer": "L6",
"label": "Security & Compliance",
"role": "impact",
"notes": "The per-caller scoping the filter was meant to enforce is neutralised by the injected value"
}
],
"attack_vector": "Attacker-controlled filter or lookup value reaching Library.block_lookup or Query.text_query_* and interpolated into a SQL WHERE clause",
"affected": "llmware 0.4.6 (latest release at the time of writing; no fixed version published)",
"impact": "Cross-document and cross-collection disclosure of rows the caller was scoped out of; boolean- and UNION-based SQL injection on the PostgreSQL backend",
"mitigations": [
"Parameterise every filter and lookup value rather than interpolating it into SQL",
"Validate filter values, not only their keys — the allow-list covers keys alone",
"Scope database credentials per tenant so a neutralised filter cannot reach other collections"
],
"external_refs": [
{
"source": "CVE",
"id": "CVE-2026-85689",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-85689"
},
{
"source": "research",
"id": "llmware-ai/llmware#1304",
"url": "https://github.com/llmware-ai/llmware/issues/1304"
}
],
"references": [
{
"title": "NVD — CVE-2026-85689",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-85689",
"type": "advisory"
},
{
"title": "VulnCheck advisory — llmware 0.4.6 SQL injection via unescaped filter values",
"url": "https://www.vulncheck.com/advisories/llmware-0.4.6-sql-injection-via-unescaped-filter-values",
"type": "advisory"
},
{
"title": "Upstream report llmware-ai/llmware#1304 (open, unfixed)",
"url": "https://github.com/llmware-ai/llmware/issues/1304",
"type": "disclosure"
}
],
"tags": [
"cve",
"tooling-cve",
"sql-injection",
"rag",
"llmware",
"cross-tenant-disclosure",
"unfixed",
"draft-mapping"
]
}
];
58 changes: 58 additions & 0 deletions scripts/incident-ids.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* Incident id allocation.
*
* INC-132 was allocated twice — by #117 and by #109 — because both read the end
* of data/incidents.json while the other was open. These cover the guard that
* now fails on a duplicate, and the helper that hands out a free id.
*/

import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import { readFileSync, writeFileSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const INCIDENTS = path.join(ROOT, 'data', 'incidents.json');
const run = (script, args = []) =>
execFileSync(process.execPath, [path.join(ROOT, 'scripts', script), ...args], { cwd: ROOT, encoding: 'utf8' });

test('the committed corpus has no duplicate incident ids', () => {
const { incidents } = JSON.parse(readFileSync(INCIDENTS, 'utf8'));
const seen = new Set();
const duplicates = incidents.map((i) => i.id).filter((id) => (seen.has(id) ? true : (seen.add(id), false)));
assert.deepEqual(duplicates, [], `duplicate incident ids: ${duplicates.join(', ')}`);
});

// The duplicate case is deliberately NOT tested by mutating data/incidents.json:
// node --test runs suites in parallel, so writing to the shared corpus races the
// other suites reading it. The guard is verified two ways instead — the corpus
// check above, and the wiring check below — and negative-tested by hand when it
// was written (injecting a duplicate made validate.js exit 1 with
// "INC-006 is used by 2 records").
test('the duplicate-id guard is wired into validate.js', () => {
const src = readFileSync(path.join(ROOT, 'scripts', 'validate.js'), 'utf8');
assert.match(src, /function checkIncidentIds\(\)/, 'the guard is missing');
assert.match(src, /^\s*checkIncidentIds\(\);/m, 'the guard is defined but never called');
assert.match(src, /is used by \$\{n\} records/, 'the guard no longer fails on a duplicate');
});

test('next-incident-id.mjs proposes an unused id', () => {
const { incidents } = JSON.parse(readFileSync(INCIDENTS, 'utf8'));
const used = new Set(incidents.map((i) => i.id));
const next = run('next-incident-id.mjs').trim();

assert.match(next, /^INC-\d{3}$/);
assert.ok(!used.has(next), `${next} is already used`);

const highest = Math.max(...incidents.map((i) => Number(String(i.id).slice(4))).filter(Number.isFinite));
assert.equal(Number(next.slice(4)), highest + 1);
});

test('--json reports what it based the answer on', () => {
const out = JSON.parse(run('next-incident-id.mjs', ['--json']));
assert.match(out.next, /^INC-\d{3}$/);
assert.match(out.highest_committed, /^INC-\d{3}$/);
assert.ok(Array.isArray(out.claimed_in_open_prs));
});
Loading
Loading