Skip to content
Open
49 changes: 48 additions & 1 deletion application/cmd/cre_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -975,6 +975,36 @@ def run(args: argparse.Namespace) -> None: # pragma: no cover
BaseParser().register_resource(
secure_headers.SecureHeaders, db_connection_str=args.cache_file
)
if args.owasp_top10_2025_in:
from application.utils.external_project_parsers.parsers import owasp_top10_2025

BaseParser().register_resource(
owasp_top10_2025.OwaspTop10_2025, db_connection_str=args.cache_file
)
if args.owasp_api_top10_2023_in:
from application.utils.external_project_parsers.parsers import (
owasp_api_top10_2023,
)

BaseParser().register_resource(
owasp_api_top10_2023.OwaspApiTop10_2023,
db_connection_str=args.cache_file,
)
if args.owasp_llm_top10_2025_in:
from application.utils.external_project_parsers.parsers import (
owasp_llm_top10_2025,
)

BaseParser().register_resource(
owasp_llm_top10_2025.OwaspLlmTop10_2025,
db_connection_str=args.cache_file,
)
if args.owasp_aisvs_in:
from application.utils.external_project_parsers.parsers import owasp_aisvs

BaseParser().register_resource(
owasp_aisvs.OwaspAisvs, db_connection_str=args.cache_file
)
if args.pci_dss_4_in:
from application.utils.external_project_parsers.parsers import pci_dss

Expand Down Expand Up @@ -1158,7 +1188,7 @@ def run_librarian(
# resolver may auto-link to (W2 seeded this from the golden set; here it is
# the real DB-backed registry).
cre_embeddings = database.get_embeddings_by_doc_type(defs.Credoctypes.CRE.value)
known_ids = set(cre_embeddings.keys())
known_ids = {cre.external_id for cre in database.get_CREs()}
# in_memory loads the hub matrix; pgvector ranks in the DB over the
# embedding_vec column (no in-RAM pool). Both honor the same retrieve().
pool = (
Expand Down Expand Up @@ -1204,6 +1234,23 @@ def run_librarian(
explicit += 1
logger.info("[explicit] %s -> %s", section.chunk_id, resolution.cre_ids[0])
continue
if resolution.outcome == ResolutionOutcome.no_reference:
# Continue to semantic retrieval below
pass
elif resolution.outcome in (
ResolutionOutcome.unknown_reference,
ResolutionOutcome.conflicting_references,
):
logger.info("[review] %s -> %s", section.chunk_id, resolution.outcome)
continue
else:
rejected += 1
logger.warning(
"[review] %s skipped: unexpected resolution outcome %s",
section.chunk_id,
resolution.outcome,
)
continue

try:
audit = retriever.retrieve(section.text)
Expand Down
4 changes: 2 additions & 2 deletions application/tests/chat_completion_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def test_completion_returns_503_json_on_gemini_429(self) -> None:
self.assertIn("error", data)
self.assertIn("rate-limited", data["error"])

def test_completion_returns_500_on_non_429_genai_error(self) -> None:
def test_completion_returns_provider_status_on_non_429_genai_error(self) -> None:
os.environ["NO_LOGIN"] = "1"
err = genai_errors.ClientError(
400,
Expand All @@ -71,7 +71,7 @@ def test_completion_returns_500_on_non_429_genai_error(self) -> None:
json={"prompt": "test"},
content_type="application/json",
)
self.assertEqual(500, response.status_code)
self.assertEqual(400, response.status_code)
data = json.loads(response.data)
self.assertIn("error", data)
self.assertIn("AI Service Error", data["error"])
Expand Down
40 changes: 14 additions & 26 deletions application/tests/fixtures/owasp_mappings/owasp_aisvs_1_0.json
Original file line number Diff line number Diff line change
@@ -1,86 +1,74 @@
[
{
"section_id": "AISVS1",
"section": "Training Data Governance & Bias Management",
"hyperlink": "https://github.com/OWASP/AISVS/tree/main/1.0/en/0x10-C01-Training-Data-Governance.md",
"section": "Training Data Integrity & Traceability",
"hyperlink": "https://github.com/OWASP/AISVS/blob/main/1.0/en/0x10-C01-Training-Data-Integrity-and-Traceability.md",
"cre_ids": ["227-045", "307-507"]
},
{
"section_id": "AISVS2",
"section": "User Input Validation",
"hyperlink": "https://github.com/OWASP/AISVS/tree/main/1.0/en/0x10-C02-User-Input-Validation.md",
"section": "Input Validation",
"hyperlink": "https://github.com/OWASP/AISVS/blob/main/1.0/en/0x10-C02-Input-Validation.md",
"cre_ids": ["031-447", "760-764"]
},
{
"section_id": "AISVS3",
"section": "Model Lifecycle Management & Change Control",
"hyperlink": "https://github.com/OWASP/AISVS/tree/main/1.0/en/0x10-C03-Model-Lifecycle-Management.md",
"hyperlink": "https://github.com/OWASP/AISVS/blob/main/1.0/en/0x10-C03-Model-Lifecycle-Management.md",
"cre_ids": ["148-853", "613-285"]
},
{
"section_id": "AISVS4",
"section": "Infrastructure, Configuration & Deployment Security",
"hyperlink": "https://github.com/OWASP/AISVS/tree/main/1.0/en/0x10-C04-Infrastructure.md",
"hyperlink": "https://github.com/OWASP/AISVS/blob/main/1.0/en/0x10-C04-Infrastructure.md",
"cre_ids": ["233-748", "486-813"]
},
{
"section_id": "AISVS5",
"section": "Access Control & Identity for AI Components & Users",
"hyperlink": "https://github.com/OWASP/AISVS/tree/main/1.0/en/0x10-C05-Access-Control-and-Identity.md",
"hyperlink": "https://github.com/OWASP/AISVS/blob/main/1.0/en/0x10-C05-Access-Control-and-Identity.md",
"cre_ids": ["633-428", "724-770"]
},
{
"section_id": "AISVS6",
"section": "Supply Chain Security for Models, Frameworks & Data",
"hyperlink": "https://github.com/OWASP/AISVS/tree/main/1.0/en/0x10-C06-Supply-Chain.md",
"hyperlink": "https://github.com/OWASP/AISVS/blob/main/1.0/en/0x10-C06-Supply-Chain.md",
"cre_ids": ["613-285", "613-287", "863-521"]
},
{
"section_id": "AISVS7",
"section": "Model Behavior, Output Control & Safety Assurance",
"hyperlink": "https://github.com/OWASP/AISVS/tree/main/1.0/en/0x10-C07-Model-Behavior.md",
"hyperlink": "https://github.com/OWASP/AISVS/blob/main/1.0/en/0x10-C07-Model-Behavior.md",
"cre_ids": ["064-808", "141-555"]
},
{
"section_id": "AISVS8",
"section": "Memory, Embeddings & Vector Database Security",
"hyperlink": "https://github.com/OWASP/AISVS/tree/main/1.0/en/0x10-C08-Memory-Embeddings-and-Vector-Database.md",
"hyperlink": "https://github.com/OWASP/AISVS/blob/main/1.0/en/0x10-C08-Memory-Embeddings-and-Vector-Database.md",
"cre_ids": ["126-668", "538-770"]
},
{
"section_id": "AISVS9",
"section": "Autonomous Orchestration & Agentic Action Security",
"hyperlink": "https://github.com/OWASP/AISVS/tree/main/1.0/en/0x10-C09-Orchestration-and-Agentic-Action.md",
"hyperlink": "https://github.com/OWASP/AISVS/blob/main/1.0/en/0x10-C09-Orchestration-and-Agentic-Action.md",
"cre_ids": ["117-371", "650-560"]
},
{
"section_id": "AISVS10",
"section": "Model Context Protocol (MCP) Security",
"hyperlink": "https://github.com/OWASP/AISVS/tree/main/1.0/en/0x10-C10-MCP-Security.md",
"hyperlink": "https://github.com/OWASP/AISVS/blob/main/1.0/en/0x10-C10-MCP-Security.md",
"cre_ids": ["307-507", "715-223"]
},
{
"section_id": "AISVS11",
"section": "Adversarial Robustness & Privacy Defense",
"hyperlink": "https://github.com/OWASP/AISVS/tree/main/1.0/en/0x10-C11-Adversarial-Robustness.md",
"hyperlink": "https://github.com/OWASP/AISVS/blob/main/1.0/en/0x10-C11-Adversarial-Robustness.md",
"cre_ids": ["141-555", "623-550"]
},
{
"section_id": "AISVS12",
"section": "Privacy Protection & Personal Data Management",
"hyperlink": "https://github.com/OWASP/AISVS/tree/main/1.0/en/0x10-C12-Privacy.md",
"cre_ids": ["126-668", "227-045", "482-866"]
},
{
"section_id": "AISVS13",
"section": "Monitoring, Logging & Anomaly Detection",
"hyperlink": "https://github.com/OWASP/AISVS/tree/main/1.0/en/0x10-C13-Monitoring-and-Logging.md",
"hyperlink": "https://github.com/OWASP/AISVS/tree/main/1.0/en/0x10-C12-Monitoring-and-Logging.md",
"cre_ids": ["058-083", "148-420", "402-706", "843-841"]
},
{
"section_id": "AISVS14",
"section": "Human Oversight, Accountability & Governance",
"hyperlink": "https://github.com/OWASP/AISVS/tree/main/1.0/en/0x10-C14-Human-Oversight.md",
"cre_ids": ["162-655", "766-162"]
}
]
62 changes: 62 additions & 0 deletions application/tests/owasp_aisvs_parser_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import unittest

from application import create_app, sqla # type: ignore
from application.database import db
from application.defs import cre_defs as defs
from application.prompt_client import prompt_client
from application.utils.external_project_parsers.parsers import owasp_aisvs


class TestOwaspAisvsParser(unittest.TestCase):
def tearDown(self) -> None:
sqla.session.remove()
sqla.drop_all()
self.app_context.pop()

def setUp(self) -> None:
self.app = create_app(mode="test")
self.app_context = self.app.app_context()
self.app_context.push()
sqla.create_all()
self.collection = db.Node_collection()

def test_parse(self) -> None:
for cre_id, name in [
("227-045", "Identify sensitive data and subject it to a policy"),
(
"307-507",
"Allow only trusted sources both build time and runtime; therefore perform integrity checks on all resources and code",
),
(
"058-083",
"Generate and retain audit logs for security events",
),
]:
self.collection.add_cre(defs.CRE(id=cre_id, name=name, description=""))

result = owasp_aisvs.OwaspAisvs().parse(
self.collection, prompt_client.PromptHandler(database=self.collection)
)

entries = result.results["OWASP AI Security Verification Standard (AISVS)"]
self.assertEqual(12, len(entries))
self.assertEqual("AISVS1", entries[0].sectionID)
self.assertEqual("Training Data Integrity & Traceability", entries[0].section)
self.assertEqual(
"https://github.com/OWASP/AISVS/blob/main/1.0/en/0x10-C01-Training-Data-Integrity-and-Traceability.md",
entries[0].hyperlink,
)
self.assertEqual(
["227-045", "307-507"],
[link.document.id for link in entries[0].links],
)
self.assertEqual("AISVS12", entries[-1].sectionID)
self.assertEqual("Monitoring, Logging & Anomaly Detection", entries[-1].section)
self.assertEqual(
"https://github.com/OWASP/AISVS/tree/main/1.0/en/0x10-C12-Monitoring-and-Logging.md",
entries[-1].hyperlink,
)
self.assertEqual(
["058-083"],
[link.document.id for link in entries[-1].links],
)
47 changes: 47 additions & 0 deletions application/tests/owasp_api_top10_2023_parser_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import unittest

from application import create_app, sqla # type: ignore
from application.database import db
from application.defs import cre_defs as defs
from application.prompt_client import prompt_client
from application.utils.external_project_parsers.parsers import owasp_api_top10_2023


class TestOwaspApiTop10_2023Parser(unittest.TestCase):
def tearDown(self) -> None:
sqla.session.remove()
sqla.drop_all()
self.app_context.pop()

def setUp(self) -> None:
self.app = create_app(mode="test")
self.app_context = self.app.app_context()
self.app_context.push()
sqla.create_all()
self.collection = db.Node_collection()

def test_parse(self) -> None:
for cre_id, name in [
("304-667", "Protect API against unauthorized access/modification (IDOR)"),
("724-770", "Technical application access control"),
("715-223", "Ensure trusted origin of third party resources"),
]:
self.collection.add_cre(defs.CRE(id=cre_id, name=name, description=""))

result = owasp_api_top10_2023.OwaspApiTop10_2023().parse(
self.collection, prompt_client.PromptHandler(database=self.collection)
)

entries = result.results["OWASP API Security Top 10 2023"]
self.assertEqual(10, len(entries))
self.assertEqual("API1", entries[0].sectionID)
self.assertEqual("Broken Object Level Authorization", entries[0].section)
self.assertEqual(
["304-667", "724-770"],
[link.document.id for link in entries[0].links],
)
self.assertEqual("API10", entries[-1].sectionID)
self.assertEqual(
["715-223"],
[link.document.id for link in entries[-1].links],
)
45 changes: 45 additions & 0 deletions application/tests/owasp_llm_top10_2025_parser_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import unittest

from application import create_app, sqla # type: ignore
from application.database import db
from application.defs import cre_defs as defs
from application.prompt_client import prompt_client
from application.utils.external_project_parsers.parsers import owasp_llm_top10_2025


class TestOwaspLlmTop10_2025Parser(unittest.TestCase):
def tearDown(self) -> None:
sqla.session.remove()
sqla.drop_all()
self.app_context.pop()

def setUp(self) -> None:
self.app = create_app(mode="test")
self.app_context = self.app.app_context()
self.app_context.push()
sqla.create_all()
self.collection = db.Node_collection()

def test_parse(self) -> None:
for cre_id, name in [
("161-451", "Output encoding and injection prevention"),
("064-808", "Encode output context-specifically"),
("760-764", "Injection protection"),
("623-550", "Denial Of Service protection"),
]:
self.collection.add_cre(defs.CRE(id=cre_id, name=name, description=""))

result = owasp_llm_top10_2025.OwaspLlmTop10_2025().parse(
self.collection, prompt_client.PromptHandler(database=self.collection)
)

entries = result.results["OWASP Top 10 for LLM and Gen AI Apps 2025"]
self.assertEqual(10, len(entries))
self.assertEqual("LLM01", entries[0].sectionID)
self.assertEqual("Prompt Injection", entries[0].section)
self.assertEqual(
["161-451", "760-764"], [link.document.id for link in entries[0].links]
)
self.assertEqual(["064-808"], [link.document.id for link in entries[4].links])
self.assertEqual("LLM10", entries[-1].sectionID)
self.assertEqual(["623-550"], [link.document.id for link in entries[-1].links])
Loading
Loading