mirror of
https://github.com/iimp0ster/detection-chokepoints
synced 2026-08-09 12:41:00 +00:00
security: harden site supply chain, escaping, Actions, and governance
XSS: - Escape `</` in all 5 jsonify-into-<script> data blobs so contributed YAML cannot break out of the script context (verified: JSON still parses, no </script breakout) - Add `| escape` to contributor-controlled fields in chokepoint-card.html and ~71 value outputs in the chokepoint detail layout Supply chain (SRI): - Pin highlight.js, d3, and Chart.js CDN includes with sha384 integrity + crossorigin (hashes computed from the immutable versioned URLs) - Document why cdn.tailwindcss.com cannot take SRI + the real fix GitHub Actions: - SHA-pin all 7 third-party actions to commit SHAs (version in comment) Governance: - SECURITY.md (private disclosure policy + scope: detection content is intentional, not a vuln) - CODEOWNERS routing review to @iimp0ster - Dependabot for github-actions / bundler / npm / pip Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
14d42ef5b5
commit
0c3709aa7e
@@ -0,0 +1,12 @@
|
||||
# Default owner for everything in the repo.
|
||||
# With branch protection requiring review, this routes PR review requests
|
||||
# to the owner automatically.
|
||||
* @iimp0ster
|
||||
|
||||
# Security-sensitive surfaces — call them out explicitly so changes here
|
||||
# are never rubber-stamped.
|
||||
/.github/ @iimp0ster
|
||||
/_config.yml @iimp0ster
|
||||
/_layouts/ @iimp0ster
|
||||
/_includes/ @iimp0ster
|
||||
/scripts/ @iimp0ster
|
||||
@@ -0,0 +1,34 @@
|
||||
version: 2
|
||||
updates:
|
||||
# Pin/refresh the GitHub Actions used in workflows (they are SHA-pinned;
|
||||
# Dependabot bumps the SHA + version comment when a new release ships).
|
||||
- package-ecosystem: github-actions
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: weekly
|
||||
commit-message:
|
||||
prefix: "ci(deps)"
|
||||
|
||||
# Ruby gems (Jekyll + plugins) via the Gemfile.
|
||||
- package-ecosystem: bundler
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: weekly
|
||||
commit-message:
|
||||
prefix: "chore(deps)"
|
||||
|
||||
# Node tooling via package.json.
|
||||
- package-ecosystem: npm
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: weekly
|
||||
commit-message:
|
||||
prefix: "chore(deps)"
|
||||
|
||||
# Python enrichment pipeline.
|
||||
- package-ecosystem: pip
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: weekly
|
||||
commit-message:
|
||||
prefix: "chore(deps)"
|
||||
@@ -12,10 +12,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
run: python scripts/aggregate.py
|
||||
|
||||
- name: Set up Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
uses: ruby/setup-ruby@89f90524b88a01fe6e0b732220432cc6142926af # v1.313.0
|
||||
with:
|
||||
ruby-version: "3.3"
|
||||
bundler-cache: true
|
||||
|
||||
@@ -21,10 +21,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
@@ -35,13 +35,13 @@ jobs:
|
||||
run: python scripts/aggregate.py
|
||||
|
||||
- name: Set up Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
uses: ruby/setup-ruby@89f90524b88a01fe6e0b732220432cc6142926af # v1.313.0
|
||||
with:
|
||||
ruby-version: "3.3"
|
||||
bundler-cache: true # runs `bundle install` and caches gems
|
||||
|
||||
- name: Configure GitHub Pages
|
||||
uses: actions/configure-pages@v5
|
||||
uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5
|
||||
|
||||
- name: Build Jekyll site
|
||||
run: bundle exec jekyll build --baseurl "/detection-chokepoints"
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
JEKYLL_ENV: production
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3
|
||||
with:
|
||||
path: "_site"
|
||||
|
||||
@@ -62,4 +62,4 @@ jobs:
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4
|
||||
|
||||
@@ -12,9 +12,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
# Restore cached DNS lookups, VT responses, and enrichment state
|
||||
# across runs so we don't re-query what we already know.
|
||||
- name: Restore enrichment cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
|
||||
with:
|
||||
path: cache/
|
||||
key: enrich-cache-${{ github.run_id }}
|
||||
|
||||
@@ -22,12 +22,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Set up Python 3.11
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
|
||||
with:
|
||||
python-version: "3.11"
|
||||
cache: pip
|
||||
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
# Security Policy
|
||||
|
||||
## Reporting a vulnerability
|
||||
|
||||
If you find a security issue in this repository — the site, the build/automation
|
||||
pipeline, or anything that could let a third party tamper with published content —
|
||||
please report it privately rather than opening a public issue.
|
||||
|
||||
- Preferred: open a [GitHub private security advisory](https://github.com/iimp0ster/detection-chokepoints/security/advisories/new).
|
||||
- Alternatively, DM [@iimp0ster](https://twitter.com/iimp0ster).
|
||||
|
||||
Please include what you found, where, and how to reproduce it. I'll acknowledge
|
||||
within a few days and keep you posted on the fix.
|
||||
|
||||
## Scope
|
||||
|
||||
In scope:
|
||||
|
||||
- Cross-site scripting or content injection via contributed chokepoint YAML,
|
||||
Sigma rules, IOK rules, or trend data rendered on the live site.
|
||||
- Supply-chain exposure in GitHub Actions workflows or site dependencies.
|
||||
- Any path that allows unauthorized modification of `main` or the deployed site.
|
||||
|
||||
Out of scope:
|
||||
|
||||
- The detection content itself. Sigma rules, command-line examples, emulation
|
||||
scripts, and IOCs are **intentional, public threat-intelligence artifacts** —
|
||||
reporting that the repo "contains attack commands" is not a vulnerability.
|
||||
- The MagicSword affiliate link and other public configuration.
|
||||
|
||||
## Contributions
|
||||
|
||||
This is a community resource. Contributions arrive by pull request and are
|
||||
reviewed before merge. Detection logic, prose, and metadata from contributed
|
||||
files are HTML-escaped at render time; if you find a field that reaches the DOM
|
||||
unescaped, that is in scope above.
|
||||
@@ -1,12 +1,12 @@
|
||||
{% assign cp = include.cp %}
|
||||
{% assign priority_class = cp.DetectionPriority | downcase %}
|
||||
<article class="chokepoint-card rounded-xl transition-all"
|
||||
data-tactic="{{ cp._tactic }}"
|
||||
data-priority="{{ cp.DetectionPriority }}"
|
||||
data-difficulty="{{ cp.Difficulty }}">
|
||||
data-tactic="{{ cp._tactic | escape }}"
|
||||
data-priority="{{ cp.DetectionPriority | escape }}"
|
||||
data-difficulty="{{ cp.Difficulty | escape }}">
|
||||
<a href="{{ cp._slug | prepend: '/chokepoints/' | append: '/' | relative_url }}"
|
||||
class="card-link flex flex-col gap-2.5 p-5 h-full"
|
||||
aria-label="View {{ cp.Name }}">
|
||||
aria-label="View {{ cp.Name | escape }}">
|
||||
|
||||
<div class="card-header flex items-center gap-2">
|
||||
<span class="priority-badge priority-{{ priority_class }} text-[.6875rem] font-bold uppercase tracking-[.06em] px-2 py-0.5 rounded-sm">
|
||||
@@ -19,17 +19,17 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<h3 class="card-title text-[1.05rem] font-bold leading-snug">{{ cp.Name }}</h3>
|
||||
<h3 class="card-title text-[1.05rem] font-bold leading-snug">{{ cp.Name | escape }}</h3>
|
||||
|
||||
<p class="card-tactic flex items-center gap-1.5 text-xs uppercase tracking-[.05em]">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/>
|
||||
</svg>
|
||||
{{ cp.Tactic }}
|
||||
{{ cp.Tactic | escape }}
|
||||
</p>
|
||||
|
||||
<p class="card-description text-sm leading-relaxed flex-grow">
|
||||
{{ cp.Description | truncate: 120 }}
|
||||
{{ cp.Description | truncate: 120 | escape }}
|
||||
</p>
|
||||
|
||||
<div class="card-meta flex items-center justify-between flex-wrap gap-2 mt-1">
|
||||
@@ -55,7 +55,7 @@
|
||||
{% if cp.TheConstant %}
|
||||
<div class="card-constant rounded px-3 py-2 mt-1">
|
||||
<span class="constant-label block text-[.6rem] font-bold uppercase tracking-[.1em] mb-1">THE CONSTANT</span>
|
||||
<span class="constant-value text-[.78rem] italic">{{ cp.TheConstant | truncate: 80 }}</span>
|
||||
<span class="constant-value text-[.78rem] italic">{{ cp.TheConstant | truncate: 80 | escape }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
@@ -61,8 +61,8 @@
|
||||
<!-- Data injection: raw groups + phases arrays; ttp-graph.js builds nodes/links -->
|
||||
<script>
|
||||
window.__TTP_GRAPH_DATA = {
|
||||
groups: {{ ttp_data.groups | jsonify }},
|
||||
phases: {{ ttp_data.phases | jsonify }}
|
||||
groups: {{ ttp_data.groups | jsonify | replace: '</', '<\/' }},
|
||||
phases: {{ ttp_data.phases | jsonify | replace: '</', '<\/' }}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
<div id="ttp-overlap-tooltip" class="ttp-tooltip" role="tooltip" aria-hidden="true"></div>
|
||||
|
||||
<script>
|
||||
window.TTP_OVERLAP_DATA = {{ ttp_data | jsonify }};
|
||||
window.TTP_OVERLAP_DATA = {{ ttp_data | jsonify | replace: '</', '<\/' }};
|
||||
</script>
|
||||
|
||||
{% endif %}
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
window.TTP_FILTER_GROUPS = {{ ttp_data.groups | jsonify }};
|
||||
window.TTP_FILTER_GROUPS = {{ ttp_data.groups | jsonify | replace: '</', '<\/' }};
|
||||
</script>
|
||||
|
||||
{% endif %}
|
||||
|
||||
@@ -196,7 +196,7 @@ layout: default
|
||||
{% if page.stages %}
|
||||
{% if page.show_ttp_overlap %}
|
||||
<script src="{{ '/assets/js/ttp-filter.js' | relative_url }}" defer></script>
|
||||
<script src="https://d3js.org/d3.v7.min.js" defer></script>
|
||||
<script src="https://d3js.org/d3.v7.min.js" integrity="sha384-CjloA8y00+1SDAUkjs099PVfnY2KmDC2BZnws9kh8D/lX1s46w6EPhpXdqMfjK6i" crossorigin="anonymous" referrerpolicy="no-referrer" defer></script>
|
||||
<script src="{{ '/assets/js/ttp-graph.js' | relative_url }}" defer></script>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
+72
-72
@@ -984,33 +984,33 @@ details.emulation-wrapper[open] .emulation-lang::after { content: ''; }
|
||||
<div class="cp-hero-inner max-w-[1100px] mx-auto px-6">
|
||||
<div class="cp-hero-top">
|
||||
<a href="{{ '/' | relative_url }}" class="cp-back-pill">← All Chokepoints</a>
|
||||
<span class="badge priority-{{ cp.DetectionPriority | downcase }}">{{ cp.DetectionPriority }}</span>
|
||||
<span class="badge priority-{{ cp.DetectionPriority | downcase | escape }}">{{ cp.DetectionPriority | escape }}</span>
|
||||
</div>
|
||||
|
||||
<h1 class="cp-hero-title">{{ cp.Name }}</h1>
|
||||
<h1 class="cp-hero-title">{{ cp.Name | escape }}</h1>
|
||||
|
||||
{% if cp.TheConstant %}
|
||||
<p class="cp-hero-constant">{{ cp.TheConstant }}</p>
|
||||
<p class="cp-hero-constant">{{ cp.TheConstant | escape }}</p>
|
||||
{% endif %}
|
||||
|
||||
<div class="cp-hero-badges">
|
||||
{% for tactic in cp.Tactics %}
|
||||
<span class="badge tactic-badge">{{ tactic }}</span>
|
||||
<span class="badge tactic-badge">{{ tactic | escape }}</span>
|
||||
{% endfor %}
|
||||
{% for mid in cp.MitreIds %}
|
||||
<a class="badge mitre-badge"
|
||||
href="https://attack.mitre.org/techniques/{{ mid | replace: '.', '/' }}/"
|
||||
target="_blank" rel="noopener">{{ mid }}</a>
|
||||
href="https://attack.mitre.org/techniques/{{ mid | replace: '.', '/' | escape }}/"
|
||||
target="_blank" rel="noopener">{{ mid | escape }}</a>
|
||||
{% endfor %}
|
||||
<span class="badge diff-badge">Detection difficulty: {{ cp.DetectionDifficulty }}</span>
|
||||
<span class="badge prev-badge">Prevalence: {{ cp.ThreatPrevalence }}</span>
|
||||
<span class="badge diff-badge">Detection difficulty: {{ cp.DetectionDifficulty | escape }}</span>
|
||||
<span class="badge prev-badge">Prevalence: {{ cp.ThreatPrevalence | escape }}</span>
|
||||
</div>
|
||||
|
||||
<p class="cp-hero-desc">{{ cp.Description }}</p>
|
||||
<p class="cp-hero-desc">{{ cp.Description | escape }}</p>
|
||||
|
||||
<p class="cp-hero-byline">
|
||||
By {{ cp.Author }} · Updated {{ cp.LastUpdated }}
|
||||
· <a href="https://github.com/{{ site.github_username }}/{{ site.github_repo }}/blob/main/{{ cp._source_path }}"
|
||||
By {{ cp.Author | escape }} · Updated {{ cp.LastUpdated | escape }}
|
||||
· <a href="https://github.com/{{ site.github_username }}/{{ site.github_repo }}/blob/main/{{ cp._source_path | escape }}"
|
||||
target="_blank" rel="noopener">View source YAML</a>
|
||||
</p>
|
||||
</div>
|
||||
@@ -1053,7 +1053,7 @@ details.emulation-wrapper[open] .emulation-lang::after { content: ''; }
|
||||
<h2 class="cp-section-heading">
|
||||
Attack Chokepoints
|
||||
{% if cp.Chokepoints %}
|
||||
<span class="section-sub">{{ cp.Chokepoints | size }} invariant stage{% if cp.Chokepoints.size != 1 %}s{% endif %}</span>
|
||||
<span class="section-sub">{{ cp.Chokepoints | size | escape }} invariant stage{% if cp.Chokepoints.size != 1 %}s{% endif %}</span>
|
||||
{% endif %}
|
||||
</h2>
|
||||
</div>
|
||||
@@ -1061,7 +1061,7 @@ details.emulation-wrapper[open] .emulation-lang::after { content: ''; }
|
||||
{% if cp.Chokepoints %}
|
||||
|
||||
{% if cp.AttackerControls or cp.AttackerCannotControl %}
|
||||
<p class="section-intro mb-4">Applying the chokepoint framework to {{ cp.Name }}.
|
||||
<p class="section-intro mb-4">Applying the chokepoint framework to {{ cp.Name | escape }}.
|
||||
<a href="{{ '/framework/' | relative_url }}">Learn the framework →</a>
|
||||
</p>
|
||||
<div class="controls-table">
|
||||
@@ -1070,7 +1070,7 @@ details.emulation-wrapper[open] .emulation-lang::after { content: ''; }
|
||||
<div class="controls-col-label">Attacker controls (variables)</div>
|
||||
<ul>
|
||||
{% for item in cp.AttackerControls %}
|
||||
<li>{{ item }}</li>
|
||||
<li>{{ item | escape }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
@@ -1080,7 +1080,7 @@ details.emulation-wrapper[open] .emulation-lang::after { content: ''; }
|
||||
<div class="controls-col-label">Attacker cannot control (chokepoints)</div>
|
||||
<ul>
|
||||
{% for item in cp.AttackerCannotControl %}
|
||||
<li>{{ item }}</li>
|
||||
<li>{{ item | escape }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
@@ -1097,7 +1097,7 @@ details.emulation-wrapper[open] .emulation-lang::after { content: ''; }
|
||||
<div class="stage-connector">
|
||||
<span class="connector-pill">
|
||||
<span class="connector-arrow">↓</span>
|
||||
{% if stage.Input %}{{ stage.Input | truncate: 60 }}{% else %}next stage{% endif %}
|
||||
{% if stage.Input %}{{ stage.Input | truncate: 60 | escape }}{% else %}next stage{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -1105,7 +1105,7 @@ details.emulation-wrapper[open] .emulation-lang::after { content: ''; }
|
||||
<details class="chokepoint-item" {% if forloop.first %}open{% endif %}>
|
||||
<summary class="chokepoint-header">
|
||||
<span class="cp-number">{{ forloop.index }}</span>
|
||||
<span class="cp-title">{{ stage.Stage }}</span>
|
||||
<span class="cp-title">{{ stage.Stage | escape }}</span>
|
||||
<span class="cp-chevron">▶</span>
|
||||
</summary>
|
||||
<div class="chokepoint-body">
|
||||
@@ -1113,7 +1113,7 @@ details.emulation-wrapper[open] .emulation-lang::after { content: ''; }
|
||||
<div class="cp-section-label">Prerequisites</div>
|
||||
<ul class="prereq-list">
|
||||
{% for prereq in cp.Prerequisites %}
|
||||
<li>{{ prereq }}</li>
|
||||
<li>{{ prereq | escape }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
@@ -1121,7 +1121,7 @@ details.emulation-wrapper[open] .emulation-lang::after { content: ''; }
|
||||
{% if stage.Input %}
|
||||
<div class="cp-row">
|
||||
<span class="cp-row-label cp-row-label-input">Input</span>
|
||||
<span class="cp-row-value">{{ stage.Input }}</span>
|
||||
<span class="cp-row-value">{{ stage.Input | escape }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@@ -1129,27 +1129,27 @@ details.emulation-wrapper[open] .emulation-lang::after { content: ''; }
|
||||
<!-- New structured format -->
|
||||
<div class="cp-row">
|
||||
<span class="cp-row-label cp-row-label-chokepoint">Chokepoint</span>
|
||||
<span class="cp-row-value cp-chokepoint-value">{{ stage.Invariant }}</span>
|
||||
<span class="cp-row-value cp-chokepoint-value">{{ stage.Invariant | escape }}</span>
|
||||
</div>
|
||||
|
||||
{% if stage.Observable %}
|
||||
<div class="cp-row">
|
||||
<span class="cp-row-label cp-row-label-observable">Observable</span>
|
||||
<span class="cp-row-value">{{ stage.Observable }}</span>
|
||||
<span class="cp-row-value">{{ stage.Observable | escape }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if stage.WhyCantBypass %}
|
||||
<div class="cp-why-unavoidable">
|
||||
<div class="cp-why-label">Why unavoidable</div>
|
||||
{{ stage.WhyCantBypass }}
|
||||
{{ stage.WhyCantBypass | escape }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<!-- Legacy format fallback -->
|
||||
<div class="cp-invariant">
|
||||
<div class="cp-invariant-label">Invariant Condition</div>
|
||||
{{ stage.Invariant }}
|
||||
{{ stage.Invariant | escape }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@@ -1157,7 +1157,7 @@ details.emulation-wrapper[open] .emulation-lang::after { content: ''; }
|
||||
<div class="cp-section-label">Data Sources</div>
|
||||
<ul class="datasource-list">
|
||||
{% for src in stage.LogSources %}
|
||||
<li>{{ src }}</li>
|
||||
<li>{{ src | escape }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
@@ -1165,7 +1165,7 @@ details.emulation-wrapper[open] .emulation-lang::after { content: ''; }
|
||||
{% if stage.BypassNote %}
|
||||
<div class="bypass-warning">
|
||||
<span class="warn-icon">⚠</span>
|
||||
<span><strong>Bypass risk:</strong> {{ stage.BypassNote }}</span>
|
||||
<span><strong>Bypass risk:</strong> {{ stage.BypassNote | escape }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@@ -1173,17 +1173,17 @@ details.emulation-wrapper[open] .emulation-lang::after { content: ''; }
|
||||
<details class="cp-true-positive">
|
||||
<summary class="cp-tp-header">
|
||||
<span class="cp-tp-dot"></span>
|
||||
<span>{{ stage.TruePositive.Title }}</span>
|
||||
<span>{{ stage.TruePositive.Title | escape }}</span>
|
||||
<span class="cp-tp-chevron">▶</span>
|
||||
</summary>
|
||||
<div class="cp-tp-body">{{ stage.TruePositive.Log | xml_escape }}{% if stage.TruePositive.KeySignal %}
|
||||
<span class="cp-tp-signal">Key signal: {{ stage.TruePositive.KeySignal }}</span>{% endif %}</div>
|
||||
<span class="cp-tp-signal">Key signal: {{ stage.TruePositive.KeySignal | escape }}</span>{% endif %}</div>
|
||||
</details>
|
||||
{% endif %}
|
||||
|
||||
{% if stage.SigmaRef and stage.SigmaRef != "" %}
|
||||
<a class="cp-rule-link"
|
||||
href="https://github.com/{{ site.github_username }}/{{ site.github_repo }}/blob/main/{{ stage.SigmaRef }}"
|
||||
href="https://github.com/{{ site.github_username }}/{{ site.github_repo }}/blob/main/{{ stage.SigmaRef | escape }}"
|
||||
target="_blank" rel="noopener">View rule →</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -1206,7 +1206,7 @@ details.emulation-wrapper[open] .emulation-lang::after { content: ''; }
|
||||
</div>
|
||||
<h2 class="cp-section-heading">
|
||||
Variations
|
||||
<span class="section-sub">{{ cp.Variations | size }} variant{% if cp.Variations.size != 1 %}s{% endif %} tracked</span>
|
||||
<span class="section-sub">{{ cp.Variations | size | escape }} variant{% if cp.Variations.size != 1 %}s{% endif %} tracked</span>
|
||||
</h2>
|
||||
</div>
|
||||
<p class="section-intro mb-4">Tools and methods that exploit this chokepoint. The list grows. The chokepoint doesn't change.</p>
|
||||
@@ -1227,9 +1227,9 @@ details.emulation-wrapper[open] .emulation-lang::after { content: ''; }
|
||||
{% endif %}
|
||||
<details class="variant-item">
|
||||
<summary class="variant-header">
|
||||
<span class="variant-name">{{ v.Name }}</span>
|
||||
<span class="variant-date">{{ v.FirstSeen }}</span>
|
||||
<span class="variant-status {{ vstatus_class }}">{{ v.Status }}</span>
|
||||
<span class="variant-name">{{ v.Name | escape }}</span>
|
||||
<span class="variant-date">{{ v.FirstSeen | escape }}</span>
|
||||
<span class="variant-status {{ vstatus_class }}">{{ v.Status | escape }}</span>
|
||||
<span class="variant-chevron">▶</span>
|
||||
</summary>
|
||||
<div class="variant-body">
|
||||
@@ -1240,13 +1240,13 @@ details.emulation-wrapper[open] .emulation-lang::after { content: ''; }
|
||||
{% if v.LurePreview %}
|
||||
<div class="variant-lure-preview">
|
||||
<span class="variant-lure-preview-label">Simulated lure</span>
|
||||
<iframe src="{{ v.LurePreview | relative_url }}" sandbox loading="lazy" title="Lure preview for {{ v.Name }}"></iframe>
|
||||
<iframe src="{{ v.LurePreview | relative_url }}" sandbox loading="lazy" title="Lure preview for {{ v.Name | escape }}"></iframe>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="variant-lure-desc">{{ v.Lure }}</div>
|
||||
<div class="variant-lure-desc">{{ v.Lure | escape }}</div>
|
||||
{% if v.LureTags %}
|
||||
<div class="variant-lure-tags">
|
||||
{% for tag in v.LureTags %}<span class="lure-tag">{{ tag }}</span>{% endfor %}
|
||||
{% for tag in v.LureTags %}<span class="lure-tag">{{ tag | escape }}</span>{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
@@ -1263,31 +1263,31 @@ details.emulation-wrapper[open] .emulation-lang::after { content: ''; }
|
||||
{% if v.Command %}
|
||||
<div class="variant-section-label variant-section-label-command">Command / artifacts</div>
|
||||
{% if v.Command.Context %}
|
||||
<div class="variant-command-context">{{ v.Command.Context }}</div>
|
||||
<div class="variant-command-context">{{ v.Command.Context | escape }}</div>
|
||||
{% endif %}
|
||||
{% if v.Command.Invocation %}
|
||||
<pre class="variant-payload"><code>{{ v.Command.Invocation | xml_escape }}</code></pre>
|
||||
{% endif %}
|
||||
{% if v.Command.Artifacts %}
|
||||
<ul class="variant-artifacts">
|
||||
{% for a in v.Command.Artifacts %}<li>{{ a }}</li>{% endfor %}
|
||||
{% for a in v.Command.Artifacts %}<li>{{ a | escape }}</li>{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if v.ChokepointMapping %}
|
||||
<div class="variant-chokepoint-map">Same chokepoint: {{ v.ChokepointMapping }}</div>
|
||||
<div class="variant-chokepoint-map">Same chokepoint: {{ v.ChokepointMapping | escape }}</div>
|
||||
{% endif %}
|
||||
|
||||
{% if v.SourceURL %}
|
||||
{% if v.SourceURL.first %}
|
||||
{% for src in v.SourceURL %}
|
||||
<a class="variant-source-link" href="{{ src }}" target="_blank" rel="noopener">
|
||||
Source: {{ src | split: '/' | slice: 2, 1 | first }} →</a>
|
||||
<a class="variant-source-link" href="{{ src | escape }}" target="_blank" rel="noopener">
|
||||
Source: {{ src | split: '/' | slice: 2, 1 | first | escape }} →</a>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<a class="variant-source-link" href="{{ v.SourceURL }}" target="_blank" rel="noopener">
|
||||
Source: {{ v.SourceURL | split: '/' | slice: 2, 1 | first }} →</a>
|
||||
<a class="variant-source-link" href="{{ v.SourceURL | escape }}" target="_blank" rel="noopener">
|
||||
Source: {{ v.SourceURL | split: '/' | slice: 2, 1 | first | escape }} →</a>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<span class="variant-source-link variant-source-link--missing">Source link needed →</span>
|
||||
@@ -1316,7 +1316,7 @@ details.emulation-wrapper[open] .emulation-lang::after { content: ''; }
|
||||
<div class="det-stage-group">
|
||||
<div class="det-stage-header">
|
||||
<span class="det-stage-num">{{ forloop.index }}</span>
|
||||
<span class="det-stage-name">{{ stage.Stage }}</span>
|
||||
<span class="det-stage-name">{{ stage.Stage | escape }}</span>
|
||||
</div>
|
||||
<div class="det-rule-list">
|
||||
|
||||
@@ -1325,12 +1325,12 @@ details.emulation-wrapper[open] .emulation-lang::after { content: ''; }
|
||||
{% for ed in cp.EarlyDetections %}
|
||||
<details class="det-rule-item">
|
||||
<summary class="det-rule-header">
|
||||
<span class="det-rule-title">{{ ed.Layer | default: "Pre-Execution Detection" }}</span>
|
||||
<span class="det-rule-title">{{ ed.Layer | default: "Pre-Execution Detection" | escape }}</span>
|
||||
<div class="det-rule-badges">
|
||||
<span class="det-rule-badge det-badge-preexec">Pre-Exec</span>
|
||||
{% if ed.ExpectedFPRate %}
|
||||
{% assign fp_lower = ed.ExpectedFPRate | downcase %}
|
||||
<span class="det-rule-badge det-badge-fp-{{ fp_lower | replace: 'medium', 'med' }}">{{ ed.ExpectedFPRate }} FP</span>
|
||||
<span class="det-rule-badge det-badge-fp-{{ fp_lower | replace: 'medium', 'med' | escape }}">{{ ed.ExpectedFPRate | escape }} FP</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<span class="det-rule-chevron">▶</span>
|
||||
@@ -1339,25 +1339,25 @@ details.emulation-wrapper[open] .emulation-lang::after { content: ''; }
|
||||
<div class="det-meta-row">
|
||||
<div class="det-meta-card">
|
||||
<div class="det-meta-label">Goal</div>
|
||||
<div class="det-meta-value">{{ ed.Description }}</div>
|
||||
<div class="det-meta-value">{{ ed.Description | escape }}</div>
|
||||
</div>
|
||||
<div class="det-meta-card">
|
||||
<div class="det-meta-label">Log Sources</div>
|
||||
<ul class="logsource-list">
|
||||
{% for src in ed.LogSources %}<li>{{ src }}</li>{% endfor %}
|
||||
{% for src in ed.LogSources %}<li>{{ src | escape }}</li>{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% if ed.Logic %}
|
||||
<div class="cp-section-label">Detection Logic</div>
|
||||
<pre class="logic-block rounded-lg p-4 overflow-x-auto text-[.8rem]"><code>{{ ed.Logic | strip }}</code></pre>
|
||||
<pre class="logic-block rounded-lg p-4 overflow-x-auto text-[.8rem]"><code>{{ ed.Logic | strip | escape }}</code></pre>
|
||||
{% endif %}
|
||||
<div class="sigma-block">
|
||||
<div class="sigma-header">
|
||||
{% if ed.IokRule %}
|
||||
<span class="sigma-label">IOK Rule - {{ ed.Layer }}</span>
|
||||
<span class="sigma-label">IOK Rule - {{ ed.Layer | escape }}</span>
|
||||
{% else %}
|
||||
<span class="sigma-label">Sigma Rule - {{ ed.Layer }}</span>
|
||||
<span class="sigma-label">Sigma Rule - {{ ed.Layer | escape }}</span>
|
||||
{% endif %}
|
||||
<div class="sigma-actions">
|
||||
{% assign ed_rule_path = ed.SigmaRule | default: ed.IokRule %}
|
||||
@@ -1436,7 +1436,7 @@ details.emulation-wrapper[open] .emulation-lang::after { content: ''; }
|
||||
<div class="det-meta-card">
|
||||
<div class="det-meta-label">Log Sources</div>
|
||||
<ul class="logsource-list">
|
||||
{% for src in det.LogSources %}<li>{{ src }}</li>{% endfor %}
|
||||
{% for src in det.LogSources %}<li>{{ src | escape }}</li>{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1552,22 +1552,22 @@ details.emulation-wrapper[open] .emulation-lang::after { content: ''; }
|
||||
{% if forloop.index > 1 %}<hr style="border:none;border-top:1px solid var(--border);margin:1.5rem 0">{% endif %}
|
||||
<div class="det-meta-row">
|
||||
<div class="det-meta-card">
|
||||
<div class="det-meta-label">Goal - {{ ed.Layer }}</div>
|
||||
<div class="det-meta-value">{{ ed.Description }}</div>
|
||||
<div class="det-meta-label">Goal - {{ ed.Layer | escape }}</div>
|
||||
<div class="det-meta-value">{{ ed.Description | escape }}</div>
|
||||
</div>
|
||||
<div class="det-meta-card">
|
||||
<div class="det-meta-label">Log Sources</div>
|
||||
<ul class="logsource-list">
|
||||
{% for src in ed.LogSources %}<li>{{ src }}</li>{% endfor %}
|
||||
{% for src in ed.LogSources %}<li>{{ src | escape }}</li>{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sigma-block">
|
||||
<div class="sigma-header">
|
||||
{% if ed.IokRule %}
|
||||
<span class="sigma-label">IOK Rule - {{ ed.Layer }}</span>
|
||||
<span class="sigma-label">IOK Rule - {{ ed.Layer | escape }}</span>
|
||||
{% else %}
|
||||
<span class="sigma-label">Sigma Rule - {{ ed.Layer }}</span>
|
||||
<span class="sigma-label">Sigma Rule - {{ ed.Layer | escape }}</span>
|
||||
{% endif %}
|
||||
<div class="sigma-actions">
|
||||
{% assign ed_rule_path = ed.SigmaRule | default: ed.IokRule %}
|
||||
@@ -1605,18 +1605,18 @@ details.emulation-wrapper[open] .emulation-lang::after { content: ''; }
|
||||
<div class="det-meta-row">
|
||||
<div class="det-meta-card">
|
||||
<div class="det-meta-label">Goal</div>
|
||||
<div class="det-meta-value">{{ detection.Description }}</div>
|
||||
<div class="det-meta-value">{{ detection.Description | escape }}</div>
|
||||
</div>
|
||||
<div class="det-meta-card">
|
||||
<div class="det-meta-label">Log Sources</div>
|
||||
<ul class="logsource-list">
|
||||
{% for src in detection.LogSources %}<li>{{ src }}</li>{% endfor %}
|
||||
{% for src in detection.LogSources %}<li>{{ src | escape }}</li>{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% if detection.Logic %}
|
||||
<div class="cp-section-label">Detection Logic</div>
|
||||
<pre class="logic-block rounded-lg p-4 overflow-x-auto text-[.8rem]"><code>{{ detection.Logic | strip }}</code></pre>
|
||||
<pre class="logic-block rounded-lg p-4 overflow-x-auto text-[.8rem]"><code>{{ detection.Logic | strip | escape }}</code></pre>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<div class="sigma-block">
|
||||
@@ -1624,8 +1624,8 @@ details.emulation-wrapper[open] .emulation-lang::after { content: ''; }
|
||||
<span class="sigma-label">Sigma Rule - {{ level }} Level</span>
|
||||
<div class="sigma-actions">
|
||||
{% if detection.SigmaRule %}
|
||||
<a class="sigma-btn" href="https://github.com/{{ site.github_username }}/{{ site.github_repo }}/blob/main/{{ detection.SigmaRule }}" target="_blank" rel="noopener">GitHub →</a>
|
||||
<a class="sigma-btn" href="https://github.com/{{ site.github_username }}/{{ site.github_repo }}/raw/main/{{ detection.SigmaRule }}" download>Download</a>
|
||||
<a class="sigma-btn" href="https://github.com/{{ site.github_username }}/{{ site.github_repo }}/blob/main/{{ detection.SigmaRule | escape }}" target="_blank" rel="noopener">GitHub →</a>
|
||||
<a class="sigma-btn" href="https://github.com/{{ site.github_username }}/{{ site.github_repo }}/raw/main/{{ detection.SigmaRule | escape }}" download>Download</a>
|
||||
{% endif %}
|
||||
<button class="sigma-btn" onclick="copyCode(this)">Copy</button>
|
||||
</div>
|
||||
@@ -1656,7 +1656,7 @@ details.emulation-wrapper[open] .emulation-lang::after { content: ''; }
|
||||
<h2 class="cp-section-heading">Prevention Opportunities</h2>
|
||||
</div>
|
||||
{% if cp.PreventionSummary %}
|
||||
<p class="section-intro mb-4">{{ cp.PreventionSummary }}</p>
|
||||
<p class="section-intro mb-4">{{ cp.PreventionSummary | escape }}</p>
|
||||
{% endif %}
|
||||
{% if cp.PreventionOpportunities %}
|
||||
<div class="prevention-grid">
|
||||
@@ -1699,9 +1699,9 @@ details.emulation-wrapper[open] .emulation-lang::after { content: ''; }
|
||||
{% for log in cp.RawLogs %}
|
||||
<details class="log-item">
|
||||
<summary class="log-header">
|
||||
{% if log.EventId %}<span class="log-eid">EID {{ log.EventId }}</span>{% endif %}
|
||||
<span class="log-source">{{ log.Type }}</span>
|
||||
<span class="log-desc">{{ log.Description }}</span>
|
||||
{% if log.EventId %}<span class="log-eid">EID {{ log.EventId | escape }}</span>{% endif %}
|
||||
<span class="log-source">{{ log.Type | escape }}</span>
|
||||
<span class="log-desc">{{ log.Description | escape }}</span>
|
||||
<span class="log-chevron">▶</span>
|
||||
</summary>
|
||||
<div class="log-body">{{ log.Sample | xml_escape }}</div>
|
||||
@@ -1770,13 +1770,13 @@ details.emulation-wrapper[open] .emulation-lang::after { content: ''; }
|
||||
<div class="osint-grid">
|
||||
{% for src in cp.OsintSources %}
|
||||
{% if src.URL %}
|
||||
<a class="osint-card rounded-xl p-4" href="{{ src.URL }}" target="_blank" rel="noopener">
|
||||
<a class="osint-card rounded-xl p-4" href="{{ src.URL | escape }}" target="_blank" rel="noopener">
|
||||
{% else %}
|
||||
<div class="osint-card rounded-xl p-4">
|
||||
{% endif %}
|
||||
<div class="osint-top">
|
||||
<span class="osint-source">{{ src.Platform }}</span>
|
||||
<code class="osint-query">{{ src.Query }}</code>
|
||||
<span class="osint-source">{{ src.Platform | escape }}</span>
|
||||
<code class="osint-query">{{ src.Query | escape }}</code>
|
||||
</div>
|
||||
{% if src.Notes %}<div class="osint-desc">{{ src.Notes | xml_escape }}</div>{% endif %}
|
||||
{% if src.URL %}</a>{% else %}</div>{% endif %}
|
||||
@@ -1831,10 +1831,10 @@ details.emulation-wrapper[open] .emulation-lang::after { content: ''; }
|
||||
|
||||
</div><!-- /.cp-page-wrap -->
|
||||
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.10.0/styles/atom-one-dark.min.css" />
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.10.0/highlight.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.10.0/languages/yaml.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.10.0/languages/powershell.min.js"></script>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.10.0/styles/atom-one-dark.min.css" integrity="sha384-oaMLBGEzBOJx3UHwac0cVndtX5fxGQIfnAeFZ35RTgqPcYlbprH9o9PUV/F8Le07" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.10.0/highlight.min.js" integrity="sha384-GdEWAbCjn+ghjX0gLx7/N1hyTVmPAjdC2OvoAA0RyNcAOhqwtT8qnbCxWle2+uJX" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.10.0/languages/yaml.min.js" integrity="sha384-bMkvdnz+wPu1ro0fqO3BaDWztc7RzSvw05MQFP6bhJKDcwpkrFYTfTFI9ndkP11l" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.10.0/languages/powershell.min.js" integrity="sha384-0u0NM3ve01ej9h9zRzZ/ztDGe1h07d6TStpNoJ4f/50I/vtoCsDHI2PfzDZSYz8q" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
hljs.highlightAll();
|
||||
|
||||
@@ -9,6 +9,11 @@
|
||||
<link rel="stylesheet" href="{{ '/assets/css/style.css' | relative_url }}" />
|
||||
<link rel="stylesheet" href="{{ '/assets/css/theme-arcade.css' | relative_url }}" />
|
||||
<link rel="icon" type="image/svg+xml" href="{{ '/assets/img/favicon.svg' | relative_url }}" />
|
||||
<!-- SECURITY: cdn.tailwindcss.com is the JIT dev build — it is a mutable,
|
||||
non-versioned script and CANNOT carry a Subresource Integrity hash, so a
|
||||
CDN compromise would execute on every page. Acceptable for now; the real
|
||||
fix is to compile Tailwind to a static, SRI-pinnable stylesheet at build
|
||||
time (tailwindcss CLI in the Pages workflow) and drop this CDN tag. -->
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<!-- Restore saved theme before first paint to prevent flash -->
|
||||
<script>
|
||||
|
||||
@@ -666,5 +666,5 @@ window.__GRAPH_DATA = (function() {
|
||||
return { nodes: nodes, links: links };
|
||||
})();
|
||||
</script>
|
||||
<script src="https://d3js.org/d3.v7.min.js"></script>
|
||||
<script src="https://d3js.org/d3.v7.min.js" integrity="sha384-CjloA8y00+1SDAUkjs099PVfnY2KmDC2BZnws9kh8D/lX1s46w6EPhpXdqMfjK6i" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<script src="{{ '/assets/js/framework-graph.js' | relative_url }}"></script>
|
||||
|
||||
+1
-1
@@ -171,7 +171,7 @@ description: "Community resource for detection engineering: high-signal chokepoi
|
||||
|
||||
<script>
|
||||
// Pass Jekyll data to JS
|
||||
window.CHOKEPOINTS_DATA = {{ site.data.chokepoints | jsonify }};
|
||||
window.CHOKEPOINTS_DATA = {{ site.data.chokepoints | jsonify | replace: '</', '<\/' }};
|
||||
window.SITE_BASEURL = "{{ site.baseurl }}";
|
||||
</script>
|
||||
<script src="{{ '/assets/js/fuse.min.js' | relative_url }}"></script>
|
||||
|
||||
@@ -1025,9 +1025,9 @@ level: critical</div>
|
||||
</div><!-- /.trends-content -->
|
||||
</div><!-- /.trends-layout -->
|
||||
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.10.0/styles/atom-one-dark.min.css">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.10.0/highlight.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.min.js"></script>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.10.0/styles/atom-one-dark.min.css" integrity="sha384-oaMLBGEzBOJx3UHwac0cVndtX5fxGQIfnAeFZ35RTgqPcYlbprH9o9PUV/F8Le07" crossorigin="anonymous" referrerpolicy="no-referrer">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.10.0/highlight.min.js" integrity="sha384-GdEWAbCjn+ghjX0gLx7/N1hyTVmPAjdC2OvoAA0RyNcAOhqwtT8qnbCxWle2+uJX" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.min.js" integrity="sha384-bs/nf9FbdNouRbMiFcrcZfLXYPKiPaGVGplVbv7dLGECccEXDW+S3zjqSKR5ZEaD" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<script>
|
||||
Chart.defaults.font.family = 'ui-monospace, monospace';
|
||||
Chart.defaults.font.size = 11;
|
||||
|
||||
Reference in New Issue
Block a user