Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Code owners for ai-dfir-detections.
# Code owners for ai-dfir-toolkit.
# These owners are requested for review on pull requests that touch matching
# paths, and are required reviewers when branch protection enforces it.

Expand Down
4 changes: 2 additions & 2 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
blank_issues_enabled: true
contact_links:
- name: Security or sensitive report
url: https://github.com/depalmar/ai-dfir-detections/blob/main/SECURITY.md
url: https://github.com/depalmar/ai-dfir-toolkit/blob/main/SECURITY.md
about: For a rule that could be weaponized, or any sensitive security concern, follow the security policy instead of opening a public issue.
- name: Contributing guide
url: https://github.com/depalmar/ai-dfir-detections/blob/main/CONTRIBUTING.md
url: https://github.com/depalmar/ai-dfir-toolkit/blob/main/CONTRIBUTING.md
about: How to submit new rules, run validation, and format Sigma / YARA / Suricata content.
57 changes: 57 additions & 0 deletions .github/ISSUE_TEMPLATE/intel_candidate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: Threat-intel candidate
description: A report, incident or advisory that may belong in the actor profiles or case studies
title: "[intel] "
labels: ["intel-candidate"]
body:
- type: markdown
attributes:
value: |
Facts and links only. AIID and OWASP material is CC BY-SA 4.0 and this repository's data is CC BY 4.0, so record ids, dates, names and URLs - not copied prose.
- type: input
id: source
attributes:
label: Primary source URL
description: The reporting party's own page, not a news summary of it
validations:
required: true
- type: input
id: reporter
attributes:
label: Reporting party
placeholder: GTIG / CERT-UA / vendor research team
validations:
required: true
- type: textarea
id: claim
attributes:
label: What the source claims, in your words
validations:
required: true
- type: textarea
id: attribution
attributes:
label: Attribution as stated
description: Quote the attributing party's wording and stated confidence verbatim. Leave blank if unattributed.
- type: input
id: tools
attributes:
label: Catalogued tools involved
placeholder: AIRT-0001
- type: textarea
id: indicators
attributes:
label: Published indicators
description: A case study needs at least one. Without any, this becomes an actor sighting.
- type: textarea
id: corroboration
attributes:
label: Independent corroboration
description: A second source that does not just repeat the first. Single-source is medium confidence at most.
- type: checkboxes
id: checks
attributes:
label: Before it reaches the catalog
options:
- label: Primary source read, not a summary
- label: Adversarial pass done (what would make this wrong?)
- label: Facts-only from CC BY-SA sources
18 changes: 18 additions & 0 deletions .github/workflows/artifacts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ jobs:
run: pip install -r requirements.txt
- name: Validate catalog and detections
run: python scripts/validate.py
# Names the project deliberately does not publish, held in a secret so the
# list itself is never committed. Passes with a notice when unset.
- name: Check for forbidden terms
env:
FORBIDDEN_TERMS: ${{ secrets.FORBIDDEN_TERMS }}
run: python scripts/forbidden_terms.py
# The schema checks technique ids by shape, so AML.T9999 and a technique
# MITRE deprecated three releases ago both pass it. This resolves them
# against pinned framework data. Offline on purpose: a gate that followed
Expand All @@ -36,6 +42,17 @@ jobs:
# change under test. Bump the pin with --refresh, which shows up in review.
- name: Resolve ATLAS and ATT&CK technique ids
run: python scripts/validate_techniques.py
# Same pattern for CISA KEV: "on KEV" is a claim a responder triages by,
# and two files once disagreed about it for the same CVE.
- name: Check KEV claims against the pinned CISA catalog
run: python scripts/cve_status.py
# MAPPINGS index tables are derived from the rows and the pinned titles.
- name: Check MAPPINGS indexes and rule tags
run: python scripts/sync_mappings.py --check
# Actor profiles and case studies against their closed schemas, and every
# cross-reference between them, the catalog and the rule corpus.
- name: Check threat-intel layer
run: python scripts/intel_graph.py --check
# The Contents block is the first thing a reader sees and every number in
# it was hand-typed and had drifted - 44 tools against 51, 258 artifacts
# against 601. Derived and gated now, like the feeds.
Expand Down Expand Up @@ -85,6 +102,7 @@ jobs:
python scripts/export_forensicartifacts.py
python scripts/export_kape.py
python scripts/export_velociraptor.py
python scripts/intel_graph.py --export
- name: Fail if feeds are stale
run: |
if ! git diff --quiet docs/api; then
Expand Down
127 changes: 127 additions & 0 deletions .github/workflows/framework-refresh.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
name: framework-refresh

# Weekly re-pin of the reference data the gates validate against: MITRE ATLAS,
# Enterprise ATT&CK and the CISA KEV catalog. Every derived file is regenerated
# and every gate CI runs is run on the refreshed tree. If the diff is confined
# to pinned data and its derivatives (scripts/refresh_guard.py), the PR is set
# to auto-merge; anything else - or a gate that fails because upstream dropped
# an id something still cites - is left for a person.
#
# Auto-merge needs, once, in the repository settings:
# - Settings > General > Allow auto-merge
# - a branch protection rule on main requiring the artifact-catalog checks
# - an AUTOMATION_TOKEN secret (fine-grained PAT or GitHub App token with
# contents + pull-requests write). PRs opened with the default GITHUB_TOKEN
# do not trigger CI, so without it the PR is opened but never auto-merges.
# Optional: a FORBIDDEN_TERMS secret (comma-separated) that holds a refresh for
# review when an added line contains any of the terms.

on:
schedule:
- cron: '17 6 * * 1' # Mondays 06:17 UTC
workflow_dispatch:

permissions:
contents: write
pull-requests: write
issues: write

concurrency:
group: framework-refresh
cancel-in-progress: false

jobs:
refresh:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: '3.11'
- name: Install dependencies
run: pip install -r artifacts/requirements.txt

- name: Re-pin ATLAS, ATT&CK and CISA KEV
working-directory: artifacts
run: |
python scripts/validate_techniques.py --refresh
python scripts/cve_status.py --refresh

- name: Regenerate everything derived from the pins
working-directory: artifacts
run: |
python scripts/sync_mappings.py
python scripts/export.py
python scripts/export_forensicartifacts.py
python scripts/export_kape.py
python scripts/export_velociraptor.py
python scripts/intel_graph.py --export
python ../collectors/gen_credential_targets.py

- name: Run every gate on the refreshed tree
working-directory: artifacts
run: |
python scripts/validate.py
python scripts/validate_techniques.py
python scripts/cve_status.py
python scripts/sync_mappings.py --check
python scripts/intel_graph.py --check
python scripts/data_sources.py --check
python scripts/export_kape.py --check
python scripts/export_velociraptor.py --check
python scripts/normalize_notes.py --check
python scripts/readme_counts.py --check
python scripts/build_site.py --check
(cd .. && python artifacts/scripts/validate_mappings.py)
(cd .. && python collectors/gen_credential_targets.py --check)
(cd .. && python collectors/check_target_drift.py)

- name: Classify the diff
id: guard
env:
FORBIDDEN_TERMS: ${{ secrets.FORBIDDEN_TERMS }}
run: |
set +e
python artifacts/scripts/refresh_guard.py --base HEAD --summary "$RUNNER_TEMP/refresh.md"
echo "code=$?" >> "$GITHUB_OUTPUT"

- name: Open or update the refresh PR
id: cpr
if: steps.guard.outputs.code == '0' || steps.guard.outputs.code == '2'
uses: peter-evans/create-pull-request@84ae59a2cdc2258d6fa0732dd66352dddae2a412 # v7.0.9
with:
token: ${{ secrets.AUTOMATION_TOKEN || secrets.GITHUB_TOKEN }}
branch: automation/framework-refresh
delete-branch: true
title: "Framework refresh: ATLAS, ATT&CK and CISA KEV pins"
body-path: ${{ runner.temp }}/refresh.md
labels: framework-refresh
commit-message: |
Refresh pinned ATLAS, ATT&CK and CISA KEV data

Generated by .github/workflows/framework-refresh.yml.

- name: Enable auto-merge
if: steps.guard.outputs.code == '0' && steps.cpr.outputs.pull-request-number != ''
env:
GH_TOKEN: ${{ secrets.AUTOMATION_TOKEN }}
PR: ${{ steps.cpr.outputs.pull-request-number }}
run: |
if [ -z "$GH_TOKEN" ]; then
echo "::notice::AUTOMATION_TOKEN is not set - PR #$PR is open for a person to merge."
exit 0
fi
gh pr merge --auto --squash "$PR" --repo "$GITHUB_REPOSITORY"

# A gate that fails on refreshed data is the case auto-merge must never
# paper over: usually upstream renamed or dropped an id that something
# here still cites. That needs a person, so it becomes an issue.
- name: Report a refresh that breaks a gate
if: failure()
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh label create framework-refresh --color 5319e7 --force --repo "$GITHUB_REPOSITORY" || true
gh issue create --repo "$GITHUB_REPOSITORY" --label framework-refresh \
--title "Framework refresh needs attention ($(date -u +%Y-%m-%d))" \
--body "The weekly refresh re-pinned upstream data and a gate failed on the result. See $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID. The usual cause is an upstream rename or removal of an id this repository cites - follow the ATLAS or ATT&CK changelog, update the citing files, and record the change in artifacts/docs/VERIFICATION.md."
40 changes: 40 additions & 0 deletions .github/workflows/intel-watch.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: intel-watch

# Weekly poll of threat-intel sources (artifacts/intel/watch-sources.yml). New
# items become issues labelled intel-candidate, with facts and a link. Nothing
# here writes to the repository or merges anything: a new incident or actor
# needs a person to read the source, find corroboration and decide scope.

on:
schedule:
- cron: '41 7 * * 3' # Wednesdays 07:41 UTC
workflow_dispatch:
inputs:
since:
description: 'ISO date to look back to (default: lookback_days in the config)'
required: false

permissions:
contents: read
issues: write

jobs:
watch:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: '3.11'
- name: Install dependencies
run: pip install -r artifacts/requirements.txt
- name: Ensure labels exist
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh label create intel-candidate --color 0e8a16 --force --repo "$GITHUB_REPOSITORY"
- name: Poll sources and open issues
working-directory: artifacts
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SINCE: ${{ github.event.inputs.since }}
run: python scripts/intel_watch.py ${SINCE:+--since "$SINCE"}
6 changes: 4 additions & 2 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ on:
- 'artifacts/docs/api/**'
- 'artifacts/case-studies/**'
- 'artifacts/detections/**'
- 'artifacts/scripts/build_site.py'
- 'artifacts/scripts/site_data.py'
- 'artifacts/intel/**'
- 'artifacts/schema/**'
- 'artifacts/scripts/*.py'
- 'artifacts/scripts/vendor/**'
- 'artifacts/requirements.txt'
- 'docs/ai-dfir-investigation-guide.md'
- 'MAPPINGS.md'
Expand Down
2 changes: 1 addition & 1 deletion 02-mcp-attacks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Detects compromise of MCP servers and clients including tool poisoning, configur

| Threat | ATLAS | OWASP | Reference |
|--------|-------|-------|-----------|
| MCP tool poisoning (description hijack, published poisoned) | T0104 | LLM03 | Invariant Labs 2025 |
| MCP tool poisoning (description hijack, published poisoned) | T0115.002 | LLM03 | Invariant Labs 2025 |
| MCP rug pull (post-install tool mutation) | T0110 | LLM04 | Invariant Labs 2025 |
| Malicious MCP config injection | T0010 | LLM03 | CVE-2025-59536 |
| MCP credential exfiltration | T0086 | LLM02 | Cyata 2025 |
Expand Down
8 changes: 5 additions & 3 deletions 02-mcp-attacks/mcp_tool_poisoning.yar
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
Date: 2026-04-15
Reference: https://invariantlabs.ai/blog/mcp-security-notification-tool-poisoning-attacks
https://www.cve.org/CVERecord?id=CVE-2025-59536
ATLAS: AML.T0104 (Publish Poisoned AI Agent Tool, Resource Development)
ATLAS: AML.T0115.002 (Publish Poisoned AI Artifacts: AI Agent Tools,
Resource Development - its own technique id until
ATLAS 2026.07 folded it in here)
AML.T0110 (AI Agent Tool Poisoning, post-install mutation)
Both, deliberately: this rule matches a poisoned tool description
wherever it lands, and cannot tell a tool published poisoned from
one mutated after approval. T0104 and T0110 are distinct current
one mutated after approval. T0115.002 and T0110 are distinct current
techniques, not alternative names - see docs/VERIFICATION.md.

Detects malicious instructions embedded in MCP tool descriptions
Expand All @@ -33,7 +35,7 @@ rule MCP_Tool_Poisoning_Hidden_Instructions
description = "MCP tool description containing hidden LLM instructions"
author = "Raymond DePalma (ai-dfir-toolkit)"
date = "2026-04-15"
atlas = "AML.T0104, AML.T0110"
atlas = "AML.T0115.002, AML.T0110"
owasp = "LLM03:2026"
reference = "https://invariantlabs.ai/blog/mcp-security-notification-tool-poisoning-attacks"
severity = "critical"
Expand Down
7 changes: 4 additions & 3 deletions 04-ai-infrastructure/ray_jobs_api_rce.rules
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
# - ShadowRay 2.0 (Nov 2025, self-propagating botnet, ~200K exposed servers)
#
# Anyscale disputes this CVE as a "design feature" — it remains exploitable
# in production. KEV-listed by CISA.
# in production. It is not in the CISA KEV catalog (CISA requires an
# available fix and Anyscale ships none); VulnCheck lists it in its own KEV.
#
# SID range: 9000200-9000299 reserved for Ray rules
#
Expand Down Expand Up @@ -48,7 +49,7 @@ alert http any any -> any any (msg:"AI-DFIR Ray Jobs API submission downloading
http.request_body; pcre:"/(curl|wget)\s+(-[^\s]*\s+)*https?:\/\/(\d{1,3}\.){3}\d{1,3}/i"; \
classtype:web-application-attack; \
reference:cve,2023-48022; \
metadata:atlas AML.T0011, atlas AML.T0019; \
metadata:atlas AML.T0011, atlas AML.T0049; \
sid:9000202; rev:1;)

# ==========================================================================
Expand All @@ -74,7 +75,7 @@ alert http any any -> any 8265 (msg:"AI-DFIR Ray Job submission referencing Node
classtype:trojan-activity; \
reference:cve,2023-48022; \
reference:url,oligo.security/blog/shadowray-2-0-attackers-turn-ai-against-itself-in-global-campaign-that-hijacks-ai-into-self-propagating-botnet; \
metadata:atlas AML.T0011, atlas AML.T0019; \
metadata:atlas AML.T0011, atlas AML.T0049; \
sid:9000204; rev:1;)

alert http any any -> any any (msg:"AI-DFIR Ray Job entrypoint contains XMRig cryptominer indicator"; \
Expand Down
Loading
Loading