From c6a8ea67684beca33e3683db420cde6562d359e6 Mon Sep 17 00:00:00 2001 From: PARTH ROHIT Date: Sun, 6 Sep 2026 03:24:14 +0100 Subject: [PATCH 1/5] docs: refresh Learn statistics on current dev Signed-off-by: parthrohit22 --- README.md | 8 ++++---- docs/learn/index.html | 48 ++++++++++++++++++++++--------------------- 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index b3f2cdd3..f0f5783e 100644 --- a/README.md +++ b/README.md @@ -59,10 +59,10 @@ Findings map to NIST FIPS 203 (ML-KEM), FIPS 204 (ML-DSA), and FIPS 205 (SLH-DSA | Feature | Description | |---|---| -| **Misconfiguration Scanner** | Runs 95 Azure security rules across storage, network, identity, database, compute, Key Vault, AKS, post-quantum cryptography, backup, serverless, private endpoint, and supply chain posture | +| **Misconfiguration Scanner** | Runs 126 Azure security rules across storage, network, identity, database, compute, Key Vault, AKS, post-quantum cryptography, backup, serverless, private endpoint, and supply chain posture | | **Compliance Mapper** | Maps findings to CIS Benchmarks, NIST CSF, ISO 27001, and SOC 2 framework JSON files | | **Scan History API** | Stores scans and findings in PostgreSQL and exposes findings, score, scan history, compliance posture, drift, and resource inventory over REST | -| **Remediation Playbooks** | Every rule ships with a matching Azure CLI remediation script (95 playbooks) | +| **Remediation Playbooks** | Every rule ships with a matching Azure CLI remediation script (126 playbooks) | | **Security Dashboard** | Full React dashboard deployed on Vercel - live monitoring, findings, compliance, drift, prioritization, and AI-layer views | | **Project Website** | Documentation and reference site at [openshield-org.github.io/openshield](https://openshield-org.github.io/openshield/) - blog, rules gallery, architecture, evidence guides, roadmap, and releases | | **Sentinel Integration** | Normalises findings and pushes them into Microsoft Sentinel via a Log Analytics custom table and KQL analytics rules | @@ -104,11 +104,11 @@ Project policies and assurance evidence: flowchart TD A["React Dashboard\nVercel · Live"] B["Flask REST API\nJWT · CORS · Blueprints"] - C["Scanner Engine\n95 Python rules"] + C["Scanner Engine\n126 Python rules"] D["Azure Subscription\nScanned via Azure SDK + Graph"] E["Compliance Framework JSON\nCIS · NIST · ISO 27001 · SOC 2"] F["PostgreSQL Database\nFindings · Scans"] - G["Azure CLI Playbooks\n95 remediation scripts"] + G["Azure CLI Playbooks\n126 remediation scripts"] H["sentinel/ingest.py\nNormalise + HMAC upload"] I["Microsoft Sentinel\nOpenShieldFindings_CL · KQL rules"] diff --git a/docs/learn/index.html b/docs/learn/index.html index 891f19ef..2ec54153 100644 --- a/docs/learn/index.html +++ b/docs/learn/index.html @@ -498,7 +498,7 @@ .severity { display: grid; - grid-template-columns: repeat(3, 1fr); + grid-template-columns: repeat(4, 1fr); gap: 12px; margin-top: 16px; } @@ -523,6 +523,7 @@ font-weight: 900; } + .critical strong { color: #f87171; } .high strong { color: #fecaca; } .medium strong { color: #fed7aa; } .low strong { color: #bbf7d0; } @@ -752,7 +753,7 @@

Learn Azure security posture with OpenShield.

openshield scan --subscription Azure

-

loading rules: 95 dynamic checks

+

loading rules: 126 dynamic checks

enrichment: NVD / CVE intelligence

storage: PostgreSQL scan history

api: Flask + JWT + CORS

@@ -764,11 +765,11 @@

Learn Azure security posture with OpenShield.
-
95Azure scan rules
-
95CLI remediation playbooks
+
126Azure scan rules
+
126CLI remediation playbooks
4Compliance frameworks
8AI security skills
-
58High-severity checks
+
82High-severity checks
@@ -821,7 +822,7 @@

Production-shaped, MVP-friendly architecture

Azure SubscriptionResources and configuration
Scanner EnginePython rule execution
-
Rule Evaluation95 dynamic checks
+
Rule Evaluation126 dynamic checks
CVE EnrichmentNVD risk context
PostgreSQLFindings and scan history
Flask APIJWT-protected REST routes
@@ -841,9 +842,9 @@

Production-shaped, MVP-friendly architecture

Rule coverage

-

95 Azure security rules

+

126 Azure security rules

- OpenShield currently has 95 dynamic rules. The strongest contributor work improves rule accuracy, reduces false positives, + OpenShield currently has 126 dynamic rules. The strongest contributor work improves rule accuracy, reduces false positives, strengthens validation, or improves remediation quality.

@@ -851,19 +852,19 @@

95 Azure security rules

Coverage by category

-
Network
23
-
Identity
15
-
Security Operations
10
-
Supply Chain
8
-
KeyVault
6
-
Kubernetes
6
-
Serverless
5
-
Storage
5
-
Backup
4
-
Compute
4
-
Database
4
-
PostQuantum
3
-
Data Link
2
+
Network
35
+
Identity
25
+
Security Operations
10
+
Supply Chain
8
+
KeyVault
6
+
Kubernetes
6
+
Compute
5
+
Serverless
5
+
Storage
9
+
Backup
4
+
Database
8
+
PostQuantum
3
+
Data Link
2
@@ -871,8 +872,9 @@

Coverage by category

Severity distribution

Most checks are high severity. That makes validation important: high-severity false positives damage trust quickly.

-
58HIGH
-
31MEDIUM
+
3CRITICAL
+
82HIGH
+
37MEDIUM
4LOW
From 5aaab487275d372b17fb2e0f93ee8bc8c768ea12 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 6 Sep 2026 02:24:23 +0000 Subject: [PATCH 2/5] docs: refresh learn page and README statistics [skip ci] Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- docs/learn/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/learn/index.html b/docs/learn/index.html index 2ec54153..9e966cb7 100644 --- a/docs/learn/index.html +++ b/docs/learn/index.html @@ -855,14 +855,14 @@

Coverage by category

Network
35
Identity
25
Security Operations
10
+
Storage
9
+
Database
8
Supply Chain
8
KeyVault
6
Kubernetes
6
Compute
5
Serverless
5
-
Storage
9
Backup
4
-
Database
8
PostQuantum
3
Data Link
2
From ad579176778a5749220f175095738759ba05b73c Mon Sep 17 00:00:00 2001 From: parthrohit22 Date: Fri, 18 Sep 2026 14:14:06 +0100 Subject: [PATCH 3/5] fix(website): deploy generated Learn route Signed-off-by: parthrohit22 --- .github/scripts/update_learn_page.py | 65 +- .github/workflows/update-learn-page.yml | 4 +- README.md | 2 +- docs/_redirects | 1 - docs/learn/index.html | 1043 ------------------- tests/test_update_learn_page.py | 57 + website/scripts/verify-site.mjs | 13 + website/src/components/JourneySection.astro | 2 +- website/src/layouts/Base.astro | 3 +- website/src/pages/learn.astro | 169 +++ 10 files changed, 286 insertions(+), 1073 deletions(-) delete mode 100644 docs/_redirects delete mode 100644 docs/learn/index.html create mode 100644 tests/test_update_learn_page.py create mode 100644 website/src/pages/learn.astro diff --git a/.github/scripts/update_learn_page.py b/.github/scripts/update_learn_page.py index 7f46240d..85df94f2 100644 --- a/.github/scripts/update_learn_page.py +++ b/.github/scripts/update_learn_page.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 -"""Refresh the rule/playbook/severity statistics in the Learn page and README. +"""Refresh repository-derived statistics in the deployed Learn page and README. -docs/learn/index.html hardcodes rule, playbook, severity and category counts +website/src/pages/learn.astro renders generated rule, playbook, severity and category counts in five places: the headline metric tiles, the hero terminal line, the pipeline step, the rules-section title/intro, the severity-box grid, and the "coverage by category" chart. README.md hardcodes the same rule and playbook @@ -22,12 +22,12 @@ import re import sys from pathlib import Path -from typing import Dict, List, Tuple +from typing import Dict, List, Optional, Tuple REPO_ROOT = Path(__file__).resolve().parents[2] RULES_DIR = REPO_ROOT / "scanner" / "rules" PLAYBOOKS_DIR = REPO_ROOT / "playbooks" / "cli" -LEARN_PAGE = REPO_ROOT / "docs" / "learn" / "index.html" +LEARN_PAGE = REPO_ROOT / "website" / "src" / "pages" / "learn.astro" README_PATH = REPO_ROOT / "README.md" # Rule modules are named az__.py. Matching on that prefix is @@ -152,6 +152,26 @@ def render_category_rows(categories: Dict[str, int]) -> str: return "\n".join(rows) +def validate_statistics(rule_count: int, severities: Dict[str, int], categories: Dict[str, int]) -> Optional[str]: + """Return an error when generated Learn totals cannot support the headline. + + The route displays CRITICAL/HIGH/MEDIUM/LOW rather than a partial severity + chart. Keep that display honest: every counted rule must occur exactly once + in both the displayed severity set and the category chart. + """ + displayed_severities = ("CRITICAL", "HIGH", "MEDIUM", "LOW") + displayed_severity_total = sum(severities[severity] for severity in displayed_severities) + severity_total = sum(severities.values()) + category_total = sum(categories.values()) + if severity_total == rule_count and displayed_severity_total == rule_count and category_total == rule_count: + return None + return ( + "generated Learn statistics do not reconcile with the rule total " + f"(rules: {rule_count}, all severities: {severity_total}, displayed " + f"CRITICAL/HIGH/MEDIUM/LOW: {displayed_severity_total}, categories: {category_total})." + ) + + def _metric(label: str) -> str: """Build the pattern for one headline metric tile on the Learn page.""" return rf'(
)\d+({re.escape(label)}
)' @@ -176,12 +196,13 @@ def render( content: str, rule_count: int, playbook_count: int, + critical_count: int, high_count: int, medium_count: int, low_count: int, category_rows: str, ) -> Tuple[str, List[str]]: - """Return (updated_content, failed_pattern_names) for docs/learn/index.html.""" + """Return (updated_content, failed_pattern_names) for the Astro Learn route.""" intro = ( r'(

\s*OpenShield currently has )\d+' r"( dynamic rules\. The strongest contributor work improves rule " @@ -193,6 +214,7 @@ def render( ) section_title = r'(

)\d+( Azure security rules

)' hero_terminal = r'(loading rules: )\d+( dynamic checks

)' + severity_critical = r'(
)\d+(CRITICAL
)' severity_high = r'(
)\d+(HIGH
)' severity_medium = r'(
)\d+(MEDIUM
)' severity_low = r'(
)\d+(LOW
)' @@ -207,6 +229,7 @@ def render( ("rules section title", section_title, rule_count), ("rules section intro paragraph", intro, rule_count), ("hero terminal: dynamic checks line", hero_terminal, rule_count), + ("severity box: CRITICAL", severity_critical, critical_count), ("severity box: HIGH", severity_high, high_count), ("severity box: MEDIUM", severity_medium, medium_count), ("severity box: LOW", severity_low, low_count), @@ -214,7 +237,7 @@ def render( content, failures = apply_replacements(content, replacements) - category_block = r'(
\n)(.*?)(\n {10}
)' + category_block = r'(
\n)(.*?)(\n\s*
)' content, count = re.subn( category_block, lambda m: m.group(1) + category_rows + m.group(3), @@ -302,29 +325,21 @@ def main() -> int: if missing_severity: print( - f"Warning: {len(missing_severity)} rule file(s) have no parseable SEVERITY " - f"and are excluded from the severity counts: {', '.join(missing_severity)}", + f"Error: {len(missing_severity)} rule file(s) have no parseable SEVERITY: {', '.join(missing_severity)}", file=sys.stderr, ) + return 1 if missing_category: print( - f"Warning: {len(missing_category)} rule file(s) have no parseable CATEGORY " - f"and are excluded from the coverage-by-category chart: {', '.join(missing_category)}", + f"Error: {len(missing_category)} rule file(s) have no parseable CATEGORY: {', '.join(missing_category)}", file=sys.stderr, ) + return 1 - chart_severities = {"HIGH", "MEDIUM", "LOW"} - excluded_severities = { - severity: count for severity, count in severities.items() if severity not in chart_severities and count - } - if excluded_severities: - excluded_detail = ", ".join(f"{severity}: {count}" for severity, count in sorted(excluded_severities.items())) - excluded_total = sum(excluded_severities.values()) - print( - f"Warning: {excluded_total} rule(s) with severities outside the " - f"HIGH/MEDIUM/LOW chart are excluded from the severity boxes: {excluded_detail}", - file=sys.stderr, - ) + statistics_error = validate_statistics(rule_count, severities, categories) + if statistics_error: + print(f"Error: {statistics_error}", file=sys.stderr) + return 1 category_rows = render_category_rows(categories) @@ -333,6 +348,7 @@ def main() -> int: learn_original, rule_count, playbook_count, + severities["CRITICAL"], severities["HIGH"], severities["MEDIUM"], severities["LOW"], @@ -342,7 +358,7 @@ def main() -> int: readme_original = README_PATH.read_text(encoding="utf-8") readme_updated, readme_failures = render_readme(readme_original, rule_count, playbook_count) - failures = [f"docs/learn/index.html -> {name}" for name in learn_failures] + failures = [f"website/src/pages/learn.astro -> {name}" for name in learn_failures] failures += [f"README.md -> {name}" for name in readme_failures] if failures: @@ -369,7 +385,8 @@ def main() -> int: print( f"Updated {', '.join(changed)} - rules: {rule_count}, playbooks: {playbook_count}, " - f"severity HIGH: {severities['HIGH']}, MEDIUM: {severities['MEDIUM']}, LOW: {severities['LOW']}" + f"severity CRITICAL: {severities['CRITICAL']}, HIGH: {severities['HIGH']}, " + f"MEDIUM: {severities['MEDIUM']}, LOW: {severities['LOW']}" ) return 0 diff --git a/.github/workflows/update-learn-page.yml b/.github/workflows/update-learn-page.yml index 92818c3c..a3499a01 100644 --- a/.github/workflows/update-learn-page.yml +++ b/.github/workflows/update-learn-page.yml @@ -31,7 +31,7 @@ jobs: - name: Detect changes id: diff run: | - if git diff --quiet -- docs/learn/index.html README.md; then + if git diff --quiet -- website/src/pages/learn.astro README.md; then echo "changed=false" >> "$GITHUB_OUTPUT" else echo "changed=true" >> "$GITHUB_OUTPUT" @@ -44,6 +44,6 @@ jobs: run: | git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add docs/learn/index.html README.md + git add website/src/pages/learn.astro README.md git commit -s -m "docs: refresh learn page and README statistics [skip ci]" git push origin "HEAD:$TARGET_REF" diff --git a/README.md b/README.md index 06a3a62c..2c467f77 100644 --- a/README.md +++ b/README.md @@ -315,7 +315,7 @@ Learn OpenShield covers: - Contributor onboarding - Documentation navigation -Live Learning Portal: https://openshieldlearn.netlify.app/learn/ +Live Learning Portal: https://owasp.github.io/openshield/learn/ Full documentation, the security rules gallery, architecture guide, evidence guide, and blog are available at the project website: **[owasp.github.io/openshield](https://owasp.github.io/openshield/)** diff --git a/docs/_redirects b/docs/_redirects deleted file mode 100644 index fe0b625a..00000000 --- a/docs/_redirects +++ /dev/null @@ -1 +0,0 @@ -/ /learn/ 302 diff --git a/docs/learn/index.html b/docs/learn/index.html deleted file mode 100644 index 9e966cb7..00000000 --- a/docs/learn/index.html +++ /dev/null @@ -1,1043 +0,0 @@ - - - - - - - OpenShield Learn - - - - - - - -
-
-
-
Open-source Azure CSPM platform
-

Learn Azure security posture with OpenShield.

-

- OpenShield scans Azure subscriptions for misconfigurations, enriches findings with CVE intelligence, - maps risks to compliance frameworks, stores scan history, exposes a Flask API, and presents results through - a React dashboard with demo and live modes. -

- -

Static learning hub. No backend, no login, no fake upload flows.

-
- - -
- -
-
126Azure scan rules
-
126CLI remediation playbooks
-
4Compliance frameworks
-
8AI security skills
-
82High-severity checks
-
-
- -
-
-
-
-

Overview

-

What OpenShield does

-

- OpenShield is built to help users identify risky Azure configurations, understand the impact, connect findings - to compliance controls, and follow practical remediation guidance. It is not a cloud provider replacement or a SIEM; - it is a focused Azure CSPM platform for posture visibility and learning. -

-
-
- -
-
-

Misconfiguration scanning

-

Dynamic Python rule modules inspect Azure resources through Azure SDK clients and return normalized security findings.

-
ScannerAzure SDKRules
-
-
-

CVE enrichment

-

Findings can be enriched with NVD/CVE context so security issues are easier to prioritize and explain.

-
NVDCVERisk context
-
-
-

Compliance mapping

-

Technical findings are mapped to CIS Azure, NIST CSF, ISO 27001, and SOC 2 for governance-oriented reporting.

-
CISNISTISO 27001SOC 2
-
-
-

Remediation guidance

-

Each rule is paired with a CLI playbook so contributors and users can move from detection to manual remediation.

-
Azure CLIPlaybooksValidation
-
-
-
- -
-

Architecture

-

Production-shaped, MVP-friendly architecture

-

- The platform follows a simple pipeline: Azure credentials are resolved by DefaultAzureCredential, the scan engine loads - rule files from scanner/rules/*.py, findings are enriched and stored, then exposed through the API and dashboard. -

- -
-
Azure SubscriptionResources and configuration
-
Scanner EnginePython rule execution
-
Rule Evaluation126 dynamic checks
-
CVE EnrichmentNVD risk context
-
PostgreSQLFindings and scan history
-
Flask APIJWT-protected REST routes
-
React DashboardDemo and live modes
-
Sentinel / AIKQL, RAG, insights
-
- -
-

Scanner

Core engine, Azure SDK wrapper, NVD/CVE enrichment, and auto-loaded rule files.

-

API

Flask REST API with JWT authentication, CORS, migrations, scans, findings, score, compliance, and AI routes.

-

Frontend

Vite, React, and Tailwind dashboard covering monitoring, discovery, prioritization, compliance, drift, and AI.

-

AI

RAG knowledge pipeline, ChromaDB vector store builder, retriever, and cloud-security knowledge skills.

-

Sentinel

Optional Log Analytics ingestion plus KQL analytics rules for detection workflows.

-

CI and docs

Checks syntax, secrets, rule structure, playbooks, compliance JSON, API syntax, and cross-references.

-
-
- -
-

Rule coverage

-

126 Azure security rules

-

- OpenShield currently has 126 dynamic rules. The strongest contributor work improves rule accuracy, reduces false positives, - strengthens validation, or improves remediation quality. -

- -
-
-

Coverage by category

-
-
Network
35
-
Identity
25
-
Security Operations
10
-
Storage
9
-
Database
8
-
Supply Chain
8
-
KeyVault
6
-
Kubernetes
6
-
Compute
5
-
Serverless
5
-
Backup
4
-
PostQuantum
3
-
Data Link
2
-
-
- -
-

Severity distribution

-

Most checks are high severity. That makes validation important: high-severity false positives damage trust quickly.

-
-
3CRITICAL
-
82HIGH
-
37MEDIUM
-
4LOW
-
-
-
-
- -
-

Learning roadmap

-

Recommended learning path

-

- Follow this path if you are new to OpenShield or preparing to contribute. Learn the security problem before touching code. -

- - -
- - - -
-

Contributors

-

Where contributors can help

-

- Good contributions should improve detection accuracy, correctness of findings, remediation quality, documentation clarity, - or system reliability. Cosmetic work is useful only when it supports those goals. -

- -
-

Rules

Add or improve Azure checks with accurate metadata, safe SDK usage, realistic test cases, and clear findings.

-

Playbooks

Keep remediation scripts aligned with rules. Every fix should include validation and avoid unsafe blanket changes.

-

Compliance

Improve CIS, NIST, ISO 27001, and SOC 2 mappings. Do not map controls just to inflate coverage.

-

Frontend

Connect live API flows carefully. Do not leave mock-backed UI pretending to be production data.

-

Backend

Implement missing endpoints consistently with JWT auth, error handling, data contracts, and PostgreSQL models.

-

AI and Sentinel

Improve RAG quality, knowledge loading, KQL rules, and ingestion without exposing sensitive findings unnecessarily.

-
-
- -
-

Known gaps

-

Current cleanup items

-

- These are not failures; they are useful follow-up targets. Documenting them prevents contributors from pretending the platform is more complete than it is. -

- -
-
-

Documentation drift

-
    -
  • Rule, playbook, and severity statistics on this page and in README.md are generated from scanner/rules/ and playbooks/cli/ by .github/scripts/update_learn_page.py on every push to dev.
  • -
  • Some startup commands assume python, but local environments may only expose python3.
  • -
  • API docs and implementation should stay aligned, especially score response shape.
  • -
-
-
-

Implementation gaps

-
    -
  • Some frontend live pages depend on endpoints that may still be mock-backed.
  • -
  • Examples include resources, drift, prioritization, and finding-specific playbook routes.
  • -
  • Fix syntax issues before claiming AI pipeline readiness.
  • -
-
-
-
- -
-

Documentation

-

Useful repo documents

-

- These relative links are intentionally static-hosting friendly when this file is served from the docs learning folder. - Adjust paths if the Learn page is moved. -

- -
-
-
ArchitectureSystem design, scanner flow, platform components, and storage/API structure.
- Open -
-
-
API ReferenceBackend routes for scans, findings, score, compliance, and AI-related data.
- Open -
-
-
Azure SetupEnvironment variables, Azure credentials, and setup requirements for live scans.
- Open -
-
-
Rules ReferenceRule metadata, categories, severity, expected output, and implementation guidance.
- Open -
-
-
Adding a RuleContributor workflow for implementing, testing, and documenting a new check.
- Open -
-
-
CI PipelineLocal and GitHub Actions checks for rules, playbooks, compliance JSON, and API syntax.
- Open -
-
-
CVE CorrelationNVD enrichment, CVSS scoring, exploit availability, and dashboard-ready CVE fields.
- Open -
-
-
Sentinel SetupLog Analytics ingestion, OpenShield findings table setup, and KQL analytics rules.
- Open -
-
-
API Render DeployRender deployment test plan, smoke testing, and production JWT requirements.
- Open -
-
-
AZ-STOR-003 Test PlanLifecycle management policy rule test setup, execution, remediation, and validation.
- Open -
-
-
- -
- Note: OpenShield Learn is a documentation and learning portal. Features such as authentication, file uploads, scan execution, and data persistence require backend services and are intentionally not implemented in this static site. -
-
- -
- -
- - - - diff --git a/tests/test_update_learn_page.py b/tests/test_update_learn_page.py new file mode 100644 index 00000000..eeb7ddc4 --- /dev/null +++ b/tests/test_update_learn_page.py @@ -0,0 +1,57 @@ +"""Focused tests for the generated Learn-route statistics contract.""" + +import importlib.util +from pathlib import Path + + +_SCRIPT = Path(__file__).parents[1] / ".github" / "scripts" / "update_learn_page.py" +_SPEC = importlib.util.spec_from_file_location("update_learn_page", _SCRIPT) +assert _SPEC and _SPEC.loader +learn = importlib.util.module_from_spec(_SPEC) +_SPEC.loader.exec_module(learn) + + +def _route_with_severity_boxes() -> str: + return """
+
1Azure scan rules
+
1CLI remediation playbooks
+
1Compliance frameworks
+
1AI security skills
+
1High-severity checks
+
+

loading rules: 1 dynamic checks

+
Rule Evaluation1 dynamic checks
+

1 Azure security rules

+

OpenShield currently has 1 dynamic rules. The strongest contributor work improves rule accuracy, reduces false positives,

+
+
Old
+
+
0CRITICAL
+
0HIGH
+
0MEDIUM
+
0LOW
""" + + +def test_render_updates_critical_box_and_requires_it(): + content, failures = learn.render(_route_with_severity_boxes(), 10, 10, 2, 5, 2, 1, "
category
") + assert failures == [] + assert '
2CRITICAL
' in content + + _, failures = learn.render( + _route_with_severity_boxes().replace("severity-box critical", "severity-box removed"), + 10, + 10, + 2, + 5, + 2, + 1, + "
category
", + ) + assert "severity box: CRITICAL" in failures + + +def test_validate_statistics_rejects_partial_severity_or_category_totals(): + severities = {"CRITICAL": 2, "HIGH": 5, "MEDIUM": 2, "LOW": 1, "INFO": 0} + assert learn.validate_statistics(10, severities, {"Network": 6, "Identity": 4}) is None + assert learn.validate_statistics(10, severities, {"Network": 9}) is not None + assert learn.validate_statistics(10, {**severities, "INFO": 1}, {"Network": 6, "Identity": 4}) is not None diff --git a/website/scripts/verify-site.mjs b/website/scripts/verify-site.mjs index 61a61cdb..cbe1037b 100644 --- a/website/scripts/verify-site.mjs +++ b/website/scripts/verify-site.mjs @@ -56,6 +56,19 @@ const index = fs.existsSync(path.join(dist, 'index.html')) : ''; if (!index.includes('Illustrative output')) failures.push('homepage does not label sample scan output'); +const learnPath = path.join(dist, 'learn', 'index.html'); +if (!fs.existsSync(learnPath)) { + failures.push('dist does not contain the /openshield/learn/ route'); +} else { + const learn = fs.readFileSync(learnPath, 'utf8'); + if (!learn.includes('https://owasp.github.io/openshield/learn/')) { + failures.push('Learn route does not have the /openshield/learn/ canonical URL'); + } + if (!learn.includes('Learn security posture from the source.')) { + failures.push('Learn route does not render the Learn experience'); + } +} + const cmsConfigPath = path.join(dist, 'admin', 'config.yml'); const adminPath = path.join(dist, 'admin', 'index.html'); const hasCmsConfig = fs.existsSync(cmsConfigPath); diff --git a/website/src/components/JourneySection.astro b/website/src/components/JourneySection.astro index 34d5de13..33e7a0db 100644 --- a/website/src/components/JourneySection.astro +++ b/website/src/components/JourneySection.astro @@ -7,7 +7,7 @@ const GITHUB = 'https://github.com/OWASP/openshield';

New here? Run it in this order.

- Learn portal → + Learn portal →
01

Set up read-only access

Authenticate, choose the subscription scope and understand where findings will be stored.

On-site quickstart
diff --git a/website/src/layouts/Base.astro b/website/src/layouts/Base.astro index ff9f1f60..6359fcc7 100644 --- a/website/src/layouts/Base.astro +++ b/website/src/layouts/Base.astro @@ -13,7 +13,7 @@ import '../styles/global.css'; interface Props { title?: string; description?: string; - /** which nav tab is active: start | rules | architecture | docs | blog | community | evidence */ + /** which nav tab is active: start | learn | rules | architecture | docs | blog | community | evidence */ active?: string; /** page-level structured data (SoftwareApplication, BlogPosting, ...) */ jsonLd?: Record; @@ -117,6 +117,7 @@ const siteJsonLd = {