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:
imposter
2026-06-12 20:24:30 -06:00
co-authored by Claude Opus 4.8
parent 14d42ef5b5
commit 0c3709aa7e
17 changed files with 191 additions and 104 deletions
+12
View File
@@ -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
+34
View File
@@ -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)"
+3 -3
View File
@@ -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
+6 -6
View File
@@ -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
+3 -3
View File
@@ -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 }}
+2 -2
View File
@@ -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
View File
@@ -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.
+8 -8
View File
@@ -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 %}
+2 -2
View File
@@ -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>
+1 -1
View File
@@ -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 %}
+1 -1
View File
@@ -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 %}
+1 -1
View File
@@ -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
View File
@@ -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">&larr; 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 }} &middot; Updated {{ cp.LastUpdated }}
&middot; <a href="https://github.com/{{ site.github_username }}/{{ site.github_repo }}/blob/main/{{ cp._source_path }}"
By {{ cp.Author | escape }} &middot; Updated {{ cp.LastUpdated | escape }}
&middot; <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 &rarr;</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">&darr;</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">&#9654;</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">&#9888;</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">&#9654;</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 &#8594;</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">&#9654;</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 }} &#8594;</a>
<a class="variant-source-link" href="{{ src | escape }}" target="_blank" rel="noopener">
Source: {{ src | split: '/' | slice: 2, 1 | first | escape }} &#8594;</a>
{% endfor %}
{% else %}
<a class="variant-source-link" href="{{ v.SourceURL }}" target="_blank" rel="noopener">
Source: {{ v.SourceURL | split: '/' | slice: 2, 1 | first }} &#8594;</a>
<a class="variant-source-link" href="{{ v.SourceURL | escape }}" target="_blank" rel="noopener">
Source: {{ v.SourceURL | split: '/' | slice: 2, 1 | first | escape }} &#8594;</a>
{% endif %}
{% else %}
<span class="variant-source-link variant-source-link--missing">Source link needed &#8594;</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">&#9654;</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 &#8594;</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 &#8594;</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">&#9654;</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();
+5
View File
@@ -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>
+1 -1
View File
@@ -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
View File
@@ -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>
+3 -3
View File
@@ -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;