Skip to content

Commit f153cda

Browse files
Merge upstream/main into fix-chatbot-error-handling-v2 and resolve chatbot.tsx conflicts
2 parents 2618e6f + a4ae0c5 commit f153cda

File tree

19 files changed

+602
-112
lines changed

19 files changed

+602
-112
lines changed

CODE_OF_CONDUCT.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Code of Conduct
2+
3+
Participation in the OpenCRE project and its community is conditional upon adhering to this Code of Conduct. By participating—including contributing code, opening or commenting on issues, participating in discussions, or otherwise engaging with the project—you agree to comply with the following.
4+
5+
This Code of Conduct is aligned with the [OWASP Foundation Code of Conduct](https://owasp.org/www-policy/operational/code-of-conduct.html) and reflects the same ethical standards expected across OWASP activities.
6+
7+
## Our Standards
8+
9+
### Conduct Generally
10+
11+
Participants are expected to:
12+
13+
- **Comply with applicable laws** — Follow all applicable local, state, national, and international laws, including export control, trade sanctions, and antitrust laws where relevant.
14+
- **Act with integrity** — Comply with high ethical principles; discharge responsibilities with diligence and honesty.
15+
- **Communicate openly and honestly** — Be constructive and transparent in technical and community discussions.
16+
- **Treat everyone with respect and dignity** — Welcome people of all backgrounds. Do not engage in harassment or any intimidating, discriminatory, abusive, derogatory, or demeaning behavior or communication.
17+
- **Respect intellectual property** — Provide proper attribution; avoid plagiarism and copyright infringement. Ensure work you submit is your own or properly attributed and permitted.
18+
- **Maintain confidentiality** — Do not disclose sensitive or proprietary information encountered in the course of project activities without authorization.
19+
- **Avoid conflicts of interest** — Refrain from activity that could constitute a conflict of interest or damage the reputation of the project, employers, or the security community.
20+
- **Reject inappropriate pressure** — Maintain objectivity and independence; reject pressure from industry or others that would compromise ethical behavior.
21+
- **Support application security** — In line with OWASP’s mission, promote the implementation of and compliance with sound application security standards and practices.
22+
23+
### Unacceptable Behavior
24+
25+
Unacceptable behavior includes, but is not limited to:
26+
27+
- **Harassment** — Any conduct a reasonable person would find unwelcome, intimidating, hostile, threatening, abusive, or offensive, including that related to gender, gender identity and expression, sexual orientation, disability, national origin, race, age, religion, or similar matters. This also includes stalking, unwelcome following, harassing photography or recording, sustained disruption of discussions or meetings, inappropriate physical contact, and unwelcome sexual attention.
28+
- **Discrimination** — Treating people unfairly based on protected characteristics or other irrelevant factors.
29+
- **Abuse or intimidation** — Personal attacks, trolling, insulting or derogatory comments, and public or private harassment.
30+
- **Misrepresentation** — Claiming others’ work as your own or misrepresenting your affiliation or authority.
31+
- **Other harmful conduct** — Any behavior that could reasonably result in harm to individuals, the project, or the broader community.
32+
33+
## Scope
34+
35+
This Code of Conduct applies to all project spaces and related activities, including:
36+
37+
- Repositories, pull requests, issues, and comments
38+
- Mailing lists, chat channels, and other communication channels used by the project
39+
- In-person or virtual events and meetings associated with OpenCRE
40+
- Any other context where you are participating as a member of the OpenCRE community
41+
42+
## Reporting
43+
44+
If you believe someone has violated this Code of Conduct, please report it. Reports will be handled with discretion and in line with the project’s commitment to a safe and respectful environment.
45+
46+
- **How to report:** Contact the project maintainers (e.g. via the repository’s contact or maintainer information, or through OWASP channels if applicable). For serious or sensitive matters, you may refer to the [OWASP Whistleblower & Anti-Retaliation Policy](https://owasp.org/www-policy/operational/whistleblower.html).
47+
- **What to include:** Your contact details (if you are comfortable sharing them), names of those involved, description of the incident, and any relevant links or evidence.
48+
- **Confidentiality:** We will keep reports confidential to the extent possible while still allowing a fair and effective response.
49+
50+
Good-faith reporters will not be retaliated against.
51+
52+
## Enforcement
53+
54+
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, issues, and other contributions that are not aligned with this Code of Conduct, and to take other actions they deem appropriate, including temporary or permanent bans from the project’s spaces.
55+
56+
- **Typical response:** For minor or first-time issues, a warning and request to change behavior may be sufficient.
57+
- **Serious or repeated violations:** May result in temporary or permanent restriction from participation (e.g. blocking from the repository, mailing list, or events).
58+
- **Escalation:** For matters that involve OWASP Foundation policies, legal issues, or broader community impact, reports may be escalated to the OWASP Foundation (e.g. Compliance Officer or Executive Director) in line with the [OWASP Code of Conduct](https://owasp.org/www-policy/operational/code-of-conduct.html) and related policies.
59+
60+
## Attribution
61+
62+
This Code of Conduct is adapted from the [OWASP Foundation Code of Conduct](https://owasp.org/www-policy/operational/code-of-conduct.html) and is intended to be consistent with OWASP’s standards for community participation.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ To install this application you need python3, yarn and virtualenv.
4646
Clone the repository:
4747

4848
```bash
49-
git clone https://github.com/OWASP/common-requirement-enumeration
49+
git clone https://github.com/OWASP/OpenCRE
5050
```
5151

5252
(Recommended) Create and activate a Python virtual environment:

application/config.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ class Config:
88
SQLALCHEMY_RECORD_QUERIES = False
99
ITEMS_PER_PAGE = 20
1010
SLOW_DB_QUERY_TIME = 0.5
11+
# Feature toggle for gap analysis optimization (default: False for safety)
12+
GAP_ANALYSIS_OPTIMIZED = (
13+
os.environ.get("GAP_ANALYSIS_OPTIMIZED", "False").lower() == "true"
14+
)
1115

1216

1317
class DevelopmentConfig(Config):

application/database/db.py

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,175 @@ def link_CRE_to_Node(self, CRE_id, node_id, link_type):
561561
raise Exception(f"Unknown relation type {link_type} for Nodes to CREs")
562562

563563
@classmethod
564+
def gap_analysis(self, name_1, name_2):
565+
"""
566+
Gap analysis with feature toggle support.
567+
568+
Toggle between original exhaustive traversal (default) and
569+
optimized tiered pruning (opt-in via GAP_ANALYSIS_OPTIMIZED env var).
570+
"""
571+
from application.config import Config
572+
573+
if Config.GAP_ANALYSIS_OPTIMIZED:
574+
logger.info(
575+
f"Gap Analysis: Using OPTIMIZED tiered pruning for {name_1}>>{name_2}"
576+
)
577+
return self._gap_analysis_optimized(name_1, name_2)
578+
else:
579+
logger.info(
580+
f"Gap Analysis: Using ORIGINAL exhaustive traversal for {name_1}>>{name_2}"
581+
)
582+
return self._gap_analysis_original(name_1, name_2)
583+
584+
@classmethod
585+
def _gap_analysis_optimized(self, name_1, name_2):
586+
"""
587+
OPTIMIZED: Tiered Pruning Strategy with Early Exit
588+
589+
Tier 1: Strong links only (LINKED_TO, SAME, AUTOMATICALLY_LINKED_TO)
590+
Tier 2: Add hierarchical (CONTAINS) if Tier 1 empty
591+
Tier 3: Fallback to wildcard if both tiers empty
592+
"""
593+
logger.info(
594+
f"Performing OPTIMIZED GraphDB queries for gap analysis {name_1}>>{name_2}"
595+
)
596+
base_standard = NeoStandard.nodes.filter(name=name_1)
597+
denylist = ["Cross-cutting concerns"]
598+
599+
# Tier 1: Strong Links (LINKED_TO, SAME, AUTOMATICALLY_LINKED_TO)
600+
path_records, _ = db.cypher_query(
601+
"""
602+
MATCH (BaseStandard:NeoStandard {name: $name1})
603+
MATCH (CompareStandard:NeoStandard {name: $name2})
604+
MATCH p = allShortestPaths((BaseStandard)-[:(LINKED_TO|AUTOMATICALLY_LINKED_TO|SAME)*..20]-(CompareStandard))
605+
WITH p
606+
WHERE length(p) > 1 AND ALL(n in NODES(p) WHERE (n:NeoCRE or n = BaseStandard or n = CompareStandard) AND NOT n.name in $denylist)
607+
RETURN p
608+
""",
609+
{"name1": name_1, "name2": name_2, "denylist": denylist},
610+
resolve_objects=True,
611+
)
612+
613+
# If strict strong links found, return early (Pruning)
614+
if path_records and len(path_records) > 0:
615+
logger.info(
616+
f"Gap Analysis: Tier 1 (Strong) found {len(path_records)} paths. Pruning remainder."
617+
)
618+
return self._format_gap_analysis_response(base_standard, path_records)
619+
620+
# Tier 2: Medium Links (Add CONTAINS to the mix)
621+
path_records, _ = db.cypher_query(
622+
"""
623+
MATCH (BaseStandard:NeoStandard {name: $name1})
624+
MATCH (CompareStandard:NeoStandard {name: $name2})
625+
MATCH p = allShortestPaths((BaseStandard)-[:(LINKED_TO|AUTOMATICALLY_LINKED_TO|SAME|CONTAINS)*..20]-(CompareStandard))
626+
WITH p
627+
WHERE length(p) > 1 AND ALL(n in NODES(p) WHERE (n:NeoCRE or n = BaseStandard or n = CompareStandard) AND NOT n.name in $denylist)
628+
RETURN p
629+
""",
630+
{"name1": name_1, "name2": name_2, "denylist": denylist},
631+
resolve_objects=True,
632+
)
633+
634+
if path_records and len(path_records) > 0:
635+
logger.info(
636+
f"Gap Analysis: Tier 2 (Medium) found {len(path_records)} paths. Pruning remainder."
637+
)
638+
return self._format_gap_analysis_response(base_standard, path_records)
639+
640+
# Tier 3: Weak/All Links (Wildcard - The original expensive query)
641+
logger.info(
642+
"Gap Analysis: Tiers 1 & 2 empty. Executing Tier 3 (Wildcard search)."
643+
)
644+
path_records_all, _ = db.cypher_query(
645+
"""
646+
MATCH (BaseStandard:NeoStandard {name: $name1})
647+
MATCH (CompareStandard:NeoStandard {name: $name2})
648+
MATCH p = allShortestPaths((BaseStandard)-[*..20]-(CompareStandard))
649+
WITH p
650+
WHERE length(p) > 1 AND ALL (n in NODES(p) where (n:NeoCRE or n = BaseStandard or n = CompareStandard) AND NOT n.name in $denylist)
651+
RETURN p
652+
""",
653+
{"name1": name_1, "name2": name_2, "denylist": denylist},
654+
resolve_objects=True,
655+
)
656+
657+
return self._format_gap_analysis_response(base_standard, path_records_all)
658+
659+
@classmethod
660+
def _gap_analysis_original(self, name_1, name_2):
661+
"""
662+
ORIGINAL: Exhaustive traversal (always runs both queries)
663+
664+
This is the safe default - maintains backward compatibility.
665+
"""
666+
logger.info(
667+
f"Performing ORIGINAL GraphDB queries for gap analysis {name_1}>>{name_2}"
668+
)
669+
base_standard = NeoStandard.nodes.filter(name=name_1)
670+
denylist = ["Cross-cutting concerns"]
671+
from datetime import datetime
672+
673+
# Query 1: Wildcard (all relationships)
674+
path_records_all, _ = db.cypher_query(
675+
"""
676+
MATCH (BaseStandard:NeoStandard {name: $name1})
677+
MATCH (CompareStandard:NeoStandard {name: $name2})
678+
MATCH p = allShortestPaths((BaseStandard)-[*..20]-(CompareStandard))
679+
WITH p
680+
WHERE length(p) > 1 AND ALL (n in NODES(p) where (n:NeoCRE or n = BaseStandard or n = CompareStandard) AND NOT n.name in $denylist)
681+
RETURN p
682+
""",
683+
{"name1": name_1, "name2": name_2, "denylist": denylist},
684+
resolve_objects=True,
685+
)
686+
687+
# Query 2: Filtered (LINKED_TO, AUTOMATICALLY_LINKED_TO, CONTAINS)
688+
path_records, _ = db.cypher_query(
689+
"""
690+
MATCH (BaseStandard:NeoStandard {name: $name1})
691+
MATCH (CompareStandard:NeoStandard {name: $name2})
692+
MATCH p = allShortestPaths((BaseStandard)-[:(LINKED_TO|AUTOMATICALLY_LINKED_TO|CONTAINS)*..20]-(CompareStandard))
693+
WITH p
694+
WHERE length(p) > 1 AND ALL(n in NODES(p) WHERE (n:NeoCRE or n = BaseStandard or n = CompareStandard) AND NOT n.name in $denylist)
695+
RETURN p
696+
""",
697+
{"name1": name_1, "name2": name_2, "denylist": denylist},
698+
resolve_objects=True,
699+
)
700+
701+
# Combine results (original behavior)
702+
def format_segment(seg: StructuredRel, nodes):
703+
relation_map = {
704+
RelatedRel: "RELATED",
705+
ContainsRel: "CONTAINS",
706+
LinkedToRel: "LINKED_TO",
707+
AutoLinkedToRel: "AUTOMATICALLY_LINKED_TO",
708+
}
709+
start_node = [
710+
node for node in nodes if node.element_id == seg._start_node_element_id
711+
][0]
712+
end_node = [
713+
node for node in nodes if node.element_id == seg._end_node_element_id
714+
][0]
715+
716+
return {
717+
"start": NEO_DB.parse_node_no_links(start_node),
718+
"end": NEO_DB.parse_node_no_links(end_node),
719+
"relationship": relation_map[type(seg)],
720+
}
721+
722+
def format_path_record(rec):
723+
return {
724+
"start": NEO_DB.parse_node_no_links(rec.start_node),
725+
"end": NEO_DB.parse_node_no_links(rec.end_node),
726+
"path": [format_segment(seg, rec.nodes) for seg in rec.relationships],
727+
}
728+
729+
return [NEO_DB.parse_node_no_links(rec) for rec in base_standard], [
730+
format_path_record(rec[0]) for rec in (path_records + path_records_all)
731+
]
732+
564733
def gap_analysis(self, name_1, name_2):
565734
logger.info(f"Performing GraphDB queries for gap analysis {name_1}>>{name_2}")
566735
base_standard = NeoStandard.nodes.filter(name=name_1)

application/frontend/src/components/DocumentNode/DocumentNode.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ export const DocumentNode: FunctionComponent<DocumentNode> = ({
8989
return (
9090
<>
9191
<span>Reference:</span>
92-
<a href={hyperlink.hyperlink} target="_blank">
92+
<a href={hyperlink.hyperlink} target="_blank" rel="noopener noreferrer">
9393
{' '}
9494
{hyperlink.hyperlink}
9595
</a>
@@ -103,7 +103,7 @@ export const DocumentNode: FunctionComponent<DocumentNode> = ({
103103
}
104104

105105
return (
106-
<a href={hyperlink.hyperlink} target="_blank">
106+
<a href={hyperlink.hyperlink} target="_blank" rel="noopener noreferrer">
107107
<Icon name="external" />
108108
</a>
109109
);

application/frontend/src/pages/CommonRequirementEnumeration/CommonRequirementEnumeration.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export const CommonRequirementEnumeration = () => {
6363
{display && display.hyperlink && (
6464
<>
6565
<span>Reference: </span>
66-
<a href={display?.hyperlink} target="_blank">
66+
<a href={display?.hyperlink} target="_blank" rel="noopener noreferrer">
6767
{' '}
6868
{display.hyperlink}
6969
</a>

application/frontend/src/pages/Explorer/LinkedStandards.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export const LinkedStandards = ({ creCode, linkedTo, applyHighlight, filter }) =
5353
{uniqueLinkedTo.map((x: LinkedTreeDocument) => (
5454
<Fragment key={x.document.name}>
5555
{isExternalLink(x, linkedTo) && (
56-
<a href={x.document.hyperlink} target="_blank">
56+
<a href={x.document.hyperlink} target="_blank" rel="noopener noreferrer">
5757
<Label>
5858
<Icon name="external" />
5959
{applyHighlight(x.document.name, filter)}

application/frontend/src/pages/Explorer/explorer.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ export const Explorer = () => {
137137
<h1>Open CRE Explorer</h1>
138138
<p>
139139
A visual explorer of Open Common Requirement Enumerations (CREs). Originally created by:{' '}
140-
<a target="_blank" href="https://zeljkoobrenovic.github.io/opencre-explorer/">
140+
<a target="_blank" rel="noopener noreferrer" href="https://zeljkoobrenovic.github.io/opencre-explorer/">
141141
Zeljko Obrenovic
142142
</a>
143143
.
@@ -158,13 +158,13 @@ export const Explorer = () => {
158158
<a href="/explorer/circles">Zoomable circles</a>
159159
</li>
160160
</ul>
161-
{/* <a target="_blank" href="visuals/force-graph-3d-contains.html">
161+
{/* <a target="_blank" rel="noopener noreferrer" href="visuals/force-graph-3d-contains.html">
162162
hierarchy only
163163
</a>
164-
<a target="_blank" href="visuals/force-graph-3d-related.html">
164+
<a target="_blank" rel="noopener noreferrer" href="visuals/force-graph-3d-related.html">
165165
related only
166166
</a>
167-
<a target="_blank" href="visuals/force-graph-3d-linked.html">
167+
<a target="_blank" rel="noopener noreferrer" href="visuals/force-graph-3d-linked.html">
168168
links to external standards
169169
</a>*/}
170170
</div>

application/frontend/src/pages/GapAnalysis/GapAnalysis.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,22 +44,22 @@ function useQuery() {
4444
const GetStrength = (score) => {
4545
if (score == 0) return 'Direct';
4646
if (score <= GA_STRONG_UPPER_LIMIT) return 'Strong';
47-
if (score >= 20) return 'Weak';
47+
if (score >= 7) return 'Weak';
4848
return 'Average';
4949
};
5050

5151
const GetStrengthColor = (score) => {
5252
if (score === 0) return 'darkgreen';
5353
if (score <= GA_STRONG_UPPER_LIMIT) return '#93C54B';
54-
if (score >= 20) return 'Red';
54+
if (score >= 7) return 'Red';
5555
return 'Orange';
5656
};
5757

5858
const GetResultLine = (path, gapAnalysis, key) => {
5959
let segmentID = gapAnalysis[key].start.id;
6060
return (
6161
<div key={path.end.id} style={{ marginBottom: '.25em', fontWeight: 'bold' }}>
62-
<a href={getInternalUrl(path.end)} target="_blank">
62+
<a href={getInternalUrl(path.end)} target="_blank" rel="noopener noreferrer">
6363
<Popup
6464
wide="very"
6565
size="large"
@@ -102,7 +102,7 @@ const GetResultLine = (path, gapAnalysis, key) => {
102102
<b style={{ color: GetStrengthColor(6) }}>{GetStrength(6)}</b>: Connected likely to have partial
103103
overlap
104104
<br />
105-
<b style={{ color: GetStrengthColor(22) }}>{GetStrength(22)}</b>: Weakly connected likely to have
105+
<b style={{ color: GetStrengthColor(7) }}>{GetStrength(7)}</b>: Weakly connected likely to have
106106
small or no overlap
107107
</Popup.Content>
108108
</Popup>
@@ -293,7 +293,7 @@ export const GapAnalysis = () => {
293293
.map((key) => (
294294
<Table.Row key={key}>
295295
<Table.Cell textAlign="left" verticalAlign="top" selectable>
296-
<a href={getInternalUrl(gapAnalysis[key].start)} target="_blank">
296+
<a href={getInternalUrl(gapAnalysis[key].start)} target="_blank" rel="noopener noreferrer">
297297
<p>
298298
<b>{getDocumentDisplayName(gapAnalysis[key].start, true)}</b>
299299
</p>

application/frontend/src/pages/Graph/Graph.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ const documentToReactFlowNode = (cDoc: Document | any): CREGraph => {
3939
position: { x: 0, y: 0 },
4040
data: {
4141
label: (
42-
<a target="_blank" href={cDoc.hyperlink}>
42+
<a target="_blank" rel="noopener noreferrer" href={cDoc.hyperlink}>
4343
{' '}
4444
{cDoc.id} - {cDoc.name}
4545
</a>
@@ -60,7 +60,7 @@ const documentToReactFlowNode = (cDoc: Document | any): CREGraph => {
6060
position: { x: 0, y: 0 },
6161
data: {
6262
label: (
63-
<a target="_blank" href={hyperlink}>
63+
<a target="_blank" rel="noopener noreferrer" href={hyperlink}>
6464
{' '}
6565
{node_label}
6666
</a>

0 commit comments

Comments
 (0)