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
49 changes: 48 additions & 1 deletion application/cmd/cre_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -1003,6 +1003,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 @@ -1198,7 +1228,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 @@ -1244,6 +1274,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