Skip to content

YurilLAB/DireC

Repository files navigation

DireC

Supply-chain file-integrity monitoring for package managers and the developers who use them.

DireC is a small, hardened, single-binary daemon that watches the directories where package managers stage and install code (npm, pip, gem, cargo, go, composer, plus the OS-level package managers), and emits a structured event the moment something on disk matches a known-bad package, dropper artefact, install-time payload, exfiltration domain, lifecycle hook, lockfile-tampering pattern, or tampered package-manager binary.

It is the defensive output of the YurilLAB Security Team's multi-year study of supply-chain compromises, and is built to be deployed as a "double-edged sword" — equally useful to individual developers protecting their workstation, and to package-manager hosts and CI runners protecting end users from a tainted publish.


Why DireC exists

Package-manager supply-chain compromise is now the dominant initial access vector for code-execution attacks against developers and CI infrastructure. The incidents bundled into the IoC database of this tree, all from 2022–2026:

event-stream / flatmap-stream • ua-parser-js / coa / rc • colors / faker maintainer sabotage • node-ipc / peacenotwar • ctx / phpass • Solana web3.js • Lottie • xz-utils (CVE-2024-3094) • Ledger ConnectKit • rspack • the qix chalk/debug phishing wave • Shai-Hulud worm waves 1, 2, 3 (Bitwarden CLI) • the SAP @cap-js TeamPCP campaign • the Sapphire Sleet axios compromise • the LiteLLM / Telnyx PyPI backdoor • tj-actions/changed-files secrets-leak

Every one of these had on-disk artefacts that would have been visible the instant the malicious package landed in node_modules/ or site-packages/, before its first require() or import ever ran. DireC is the watcher that surfaces those artefacts.

The companion research document YurilLAB Supply Chain.docx covers the same incidents in depth; the IoC database in src/iocs.c is kept in sync with that research; and the per-incident write-ups in docs/ trace each artefact to the detection that catches it.


Threat model

In scope.

  • Tampering with the package-manager binary itself (npm, pip, gem, cargo, go, composer, dpkg, apt, rpm, dnf, pacman, brew, snap, flatpak) — including the case where the canonical path is a symlink, which is what every distro actually ships.
  • Tampering with manifests and lockfiles (package.json, package-lock.json, npm-shrinkwrap.json, yarn.lock, pnpm-lock.yaml, requirements.txt, Pipfile.lock, poetry.lock, pyproject.toml, Cargo.toml, Cargo.lock, Gemfile.lock, composer.json, composer.lock, go.mod, go.sum).
  • Lockfile structural attacks: integrity-hash stripping, non-default resolved URLs, git+/file: non-registry sources.
  • All sixteen npm lifecycle hooks (preinstall, install, postinstall, prepare, prepublish, prepublishOnly, preprepare, postprepare, prerestore, postrestore, prepack, postpack, preversion, postversion, predeploy, postdeploy).
  • Worm-class auto-republication patterns (Shai-Hulud / TeamPCP): npm publish / npm view / npm access / npm version driven from inside a lifecycle hook, plus the dead-man's-switch wiper variants (--no-preserve-root, rm -rf / family).
  • Bun-runtime evasion (Shai-Hulud 2.0): bun install / bun run / bun x invoked from a lifecycle hook to bypass Node.js-tuned sandboxes.
  • AI-weaponised supply-chain attacks: AI CLI guardrail-bypass flags (--dangerously-skip-permissions, --trust-all-tools, --yolo) inside an installed package — Nx/s1ngularity (Aug 2025); rogue Model Context Protocol server artefacts in AI-tool directories — SANDWORM_MODE (Feb 2026); local AI service port-probing (Ollama :11434, generic 127.0.0.1:1234/5000/8000/8080).
  • CI/CD trigger and runner abuse: pull_request_target co-occurring with actions/checkout and secrets.* (HackerBot-Claw, Feb–Mar 2026); expression-injection of untrusted PR fields into run: steps (Ultralytics, Dec 2024); self-hosted runner registration inside a workflow (Shai-Hulud 2.0 wave-2 SHA1HULUD persistence).
  • PhantomRaven Remote Dynamic Dependencies: a package.json dependency specifier that is an http(s):// URL, bypassing every registry-side static-analysis control.
  • Blockchain command-and-control: the GlassWorm primary Solana C2 address as a literal in package source.
  • Python setup.py arbitrary-code (the single biggest difference from npm — pip install runs full Python) and .pth import-hijack files (the TeamPCP litellm_init.pth shape).
  • Cargo build.rs arbitrary-code attacks.
  • Per-victim credential exfiltration patterns: GitHub PAT, AWS access key, Stripe live key, Slack token, Discord webhook URL, Telegram bot API URL.
  • Configuration redirection: .npmrc / .yarnrc registry= override, package.json publishConfig.registry override, npm bin field path traversal.
  • GitHub Actions workflow tampering (the tj-actions/changed-files March-2025 secrets-print pattern).
  • CVE-2021-42574 "Trojan Source" Bidi/RLO Unicode in source.
  • Cross-ecosystem pollution (Python files inside node_modules/, JS/EXE inside site-packages/).
  • OS-level dropper artefacts (liblzma.so.5.6.0, litellm_init.pth, 6202033.vbs, bun_environment.js, bw_setup.js, bw1.js, discussion.yaml, ...).
  • Metadata-only tampering: SUID/SGID bit flips, chown, setcap cap_net_admin+ep, SELinux relabels, xattr injection.
  • Kernel-level page-cache writes (CVE-2026-31431 "Copy Fail", Apr 2026, every Linux distro since 2017). The hash-changes-but- mtime-doesn't signature is detected as system.page-cache-tamper at SEV_ERR with the CVE id and upstream patch reference; the exploit script's literal strings (authencesn(, socket(AF_ALG, the CVE id itself) are also IoC-tagged.
  • High-value system regions outside package-manager directories: PAM modules, sudoers, sshd configs, /etc/passwd / /etc/shadow, systemd unit dirs, cron drop-ins, /var/spool/cron, kernel modules, modprobe / sysctl / udev configs, GRUB config, profile scripts, polkit / D-Bus, CA cert bundles, firewall rules. macOS: LaunchDaemons / LaunchAgents / kexts. Windows: Scheduled Tasks / drivers / registry hives / Startup folder / Group Policy. Each registered region fires its own semantic event (system.pam- modified, system.cron-modified, etc.) at the appropriate severity.
  • Detection accuracy: every supply.* event runs through the 7-identifier weighted scorer (src/score.c) and carries score=N tier=LABEL verify=N in its note so SIEM rules can route by confidence band. Below-threshold SEV_WARN events are suppressed (logged at INFO so the audit trail still exists); SEV_ERR events always fire, scoring can never hide a critical alert.
  • Mid-scan race-edits and TOCTOU: read_capped records the buffer's (inode, mtime, size) before scanning; at every emit the path is re-lstat'd and the result feeds I_VERIFY. A race- edit, decoy swap, or attacker rewriting the file between read and emit shows up as verify=70 (mtime drift), verify=60 (size drift), or verify=30 (inode swap / vanished).
  • Daemon survivability against malware: the daemon hardens itself in-process (Linux: PR_SET_DUMPABLE=0, PR_SET_NO_NEW_PRIVS, OOM exemption; Windows: process DACL blocks PROCESS_TERMINATE for non-admin SIDs, mitigation policies block code injection / extension-point hijacking). Optional --supervisor mode auto-restarts the worker on abnormal exit; under systemd the unit ships Restart=always.

Out of scope.

  • Process-level attribution. DireC is filesystem-resident and does not see which process wrote a file. Pair with auditd, eBPF, or Falco for pid attribution.
  • Browser-side wallet drainer behaviour (the qix payload activates only inside a browser). DireC catches the on-disk artefact and the exfil domain; the runtime hook is detectable only by an instrumented browser.
  • Network-level exfil interception. DireC notes the exfil domain in the file content; blocking the connection is a job for a host firewall or egress proxy.
  • Cryptographic authenticity of upstream tarballs. npm itself does not require signed tarballs; DireC sees the result on disk and tells you it changed. End-to-end authenticity is complementary (Sigstore, npm provenance, pinned commit SHAs).

Detection classes

Event kind What it catches
supply.package Known-compromised <package>@<version> in any lockfile or manifest
supply.install-script Suspicious token or OOB domain inside any of the 16 npm lifecycle hooks
supply.lockfile-integrity-strip package-lock.json / npm-shrinkwrap.json with many resolved URLs but few/zero integrity SRI hashes
supply.lockfile-non-registry git+, file:, git@github.com: source in lockfile
supply.registry Lockfile resolved URLs not pointing to registry.npmjs.org
supply.npmrc-registry .npmrc / .yarnrc / .yarnrc.yml registry= (or :registry= / npmRegistryServer:) line redirected away from registry.npmjs.org. The check confirms the literal assignment-to-default is present, so a comment mentioning registry.npmjs.org cannot mask an actual hostile-mirror redirect on a different line
supply.pipconf-index pip.conf / pip.ini index-url (or extra-index-url) line redirected away from pypi.org — the Python-side equivalent of the npmrc primitive
supply.composer-script composer.json references an OOB exfil domain, suspicious token, embedded credential, or bidi/RLO Unicode — composer's lifecycle event keys (pre-install-cmd / post-install-cmd / pre-update-cmd / etc.) are the analogue of npm's lifecycle hooks
supply.cargo-non-registry Cargo.toml [source.crates-io] replace-with redirect, OR Cargo.lock source = "registry+... not pointing to the canonical crates.io-index, OR source = "git+..." dependency. The Rust-side equivalent of the npm/lockfile non-registry detection
supply.gemfile-source Bundler Gemfile source directive not pointing to rubygems.org — the Ruby-side equivalent of supply.npmrc-registry and supply.pipconf-index
fim.watch-path-missing One-shot warning when a configured top-level watch = path becomes unreadable (operator-visibility fail-safe — without this the daemon would silently produce zero events from that tree)
fim.watch-path-recovered Info-level signal when a previously-missing watch path returns to readable
daemon.backend-error The active backend's run loop returned an error; daemon is shutting down with non-clean status (was previously a silent exit)
supply.publish-config package.json publishConfig.registry redirect
supply.bin-traversal package.json bin field contains ../
supply.token-leak npm _authToken= reference outside its only legitimate home (.npmrc / .yarnrc)
supply.credential-leak High-confidence credential prefix (GitHub PAT, AWS, Stripe, Slack, Discord, Telegram) embedded in package code
supply.setup-py setup.py references OOB domain, suspicious token, credential, or bidi char
supply.build-rs Cargo build.rs references OOB domain, suspicious token, credential, or bidi char
supply.pth-payload Python .pth file containing exec/eval/__import__/socket/subprocess/... (TeamPCP litellm_init.pth)
supply.cross-ecosystem .py inside node_modules/ or .js/.exe inside site-packages/
supply.ai-bypass-flag (SEV ERROR) AI CLI guardrail-bypass flag (--dangerously-skip-permissions, --trust-all-tools, --yolo) inside an installed package — Nx/s1ngularity (Aug 2025), 2,349 secrets exfiltrated by driving the locally-installed Claude Code / Gemini CLI / Amazon Q into recursive enumeration
supply.mcp-server (SEV ERROR) Rogue Model Context Protocol server filename (mcp-server-*, .mcp.json, mcp_config*, claude_mcp_*) appearing in an AI-tool directory (~/.dev-utils/, ~/.cursor/mcp/, ~/.config/claude/mcp/, ~/.codeium/, ~/.local/share/mcp/) — SANDWORM_MODE (Feb 2026)
supply.remote-dynamic-dependency package.json declares a dependency whose version specifier is an http:// or https:// URL — PhantomRaven Remote Dynamic Dependency shape
supply.workflow-prt-secrets (SEV ERROR) .github/workflows/*.yml combines pull_request_target + actions/checkout@v + secrets.* access — HackerBot-Claw (Feb–Mar 2026), exploited in 6–7 major repos in 37 hours
supply.workflow-inject Workflow interpolates an untrusted PR field (github.head_ref, github.event.pull_request.title/body, github.event.issue/discussion.title) into a run: step — Ultralytics (Dec 2024)
supply.self-hosted-runner (SEV ERROR) Workflow registers a self-hosted runner (runs-on: self-hosted + config.sh/./run.sh/actions-runner/SHA1HULUD) — Shai-Hulud 2.0 wave-2 persistence
supply.workflow .github/workflows/*.yml references OOB domain, pipes remote shell, or prints ${{ secrets.* }} through base64 (tj-actions style)
supply.os-file OS-level dropper filename match (xz-utils, TeamPCP, Shai-Hulud, ...)
supply.oob-domain Out-of-band exfil domain in any textual file inside a package-manager tree
supply.token Suspicious code substring (eval(Buffer.from(, curl -fsSL, | bash, ...)
supply.bidi CVE-2021-42574 Bidi/RLO Unicode control characters in source
supply.unicode-injection Invisible-Unicode steganography — variation selectors U+FE00U+FE0F, Variation Selectors Supplement, Tag characters, Private Use Area BMP+Supp. Anchor: GlassWorm (Oct 2025–Mar 2026, 35,800+ VS Code installations affected)
supply.sri-missing HTML/JSX/Vue template loads multiple external resources (<script src="https://..."> / <link href="...">) without paired integrity= attributes. Anchor: Bybit/Safe{Wallet} $1.5 B heist (Feb 2025)
supply.typosquat Bounded Levenshtein hit against the popular npm / PyPI name list, dotfile-internals (.bin, .cache, ...) skipped
pm.binary-baseline INFO, once at startup per detected PM binary
pm.binary-changed ERROR, on any change to a fingerprinted PM binary; symlinks are followed and the readlink target is mixed into the hash so a symlink swap also flips the fingerprint
pm.binary-deleted ERROR, the PM binary disappeared between cycles
pm.binary-restored INFO, the PM binary returned to disk after an absence (paired with the prior pm.binary-deleted)
pm.update-detected INFO, per-cycle summary fired when many creates/modifies land under one package manager's state directories or its binary changed in the same cycle — flags "this looks like an npm install / apt upgrade happened" so downstream supply.* hits can be correlated with the install context
system.page-cache-tamper (SEV ERROR) Hash differs but mtime, size, and mode are all unchanged — the unique footprint of a kernel-level page-cache write primitive (CVE-2026-31431 "Copy Fail" and equivalents). The note carries the CVE id and upstream patch reference; requires paranoid = yes to detect because the default fast-path skips re-hashing when stat is unchanged
system.pam-modified, system.sudoers-modified, system.sshd-config-modified, system.passwd-modified, system.shadow-modified, system.security-config-modified, system.systemd-unit-modified, system.cron-modified, system.kernel-modules-modified, system.modprobe-config-modified, system.sysctl-modified, system.udev-modified, system.hosts-modified, system.resolv-modified, system.nsswitch-modified, system.ca-certs-modified, system.firewall-modified, system.grub-modified, system.profile-modified, system.polkit-modified, system.dbus-modified, system.launchdaemon-modified, system.launchagent-modified, system.kext-modified, system.scheduled-task-modified, system.driver-modified, system.registry-hive-modified, system.startup-modified, system.gpo-modified Semantic file-integrity events fired when a fim.create / fim.modify lands on a path registered in src/protect.c. CRITICAL-tier paths fire SEV_ERROR; SENSITIVE / NORMAL fire SEV_WARN. The generic fim.* event is preserved alongside, so existing SIEM rules keep working
forensics.profile INFO, paired with each supply.* hit. Carries SHA-256 (full file or sha256-prefix= if file > 1 MiB), size, line count, magic-bytes hex, Shannon entropy, hit count, co-occurrence score, and the first triggered kind — everything an IR responder needs to triage off-host
beige.campaign-detected INFO/WARN/ERROR depending on the matched signature, fired by the BEIGE temporal-correlation engine when a sliding-window event cluster matches one of the 8 hard-coded multi-stage attack patterns (Shai-Hulud propagation, TeamPCP cascade, GlassWorm staging, Bybit SRI strip + supply hit, etc.). Single events score in isolation; BEIGE turns "this package.json mentioned chalk" + "this index.js leaked a token" + "this .npmrc redirected the registry" — all within 5 minutes on the same package root — into one named campaign
daemon.start / daemon.stop Lifecycle. The start event embeds the host fingerprint (host=… kernel=… arch=… init=…) so the JSONL sink "remembers" which host each event came from
daemon.backend-error The active backend's run loop returned an error; daemon is shutting down with non-clean status (was previously a silent exit)
fim.create / fim.modify / fim.metadata / fim.delete Generic file-integrity events from the active backend
fim.watch-path-missing / fim.watch-path-recovered One-shot warnings when a configured top-level watch = path becomes / returns from unreadable (operator-visibility fail-safe — without this the daemon would silently produce zero events from that tree)

A stage-by-stage map of every npm install lifecycle event to the DireC detection that catches it lives in docs/npm-protection.md.


Architecture

                ┌──────────────┐    ┌──────────────────┐
                │  direc.conf  │    │  --supervisor    │
                │  (parsed,    │    │  (optional       │
                │   bounded,   │    │   wrapper proc.; │
                │   validated) │    │   restarts on    │
                └──────┬───────┘    │   abnormal exit) │
                       │            └──────┬───────────┘
                       ▼                   │ fork+execvp
                       │                   ▼
   ┌────────────────────────────────────────────────────────────┐
   │             direcd (single C11 binary)                     │
   │                                                            │
   │   ┌──────────────────────────────────────────────────────┐ │
   │   │  harden.c   self-hardening applied EARLY:            │ │
   │   │  Linux: PR_SET_NO_NEW_PRIVS, PR_SET_DUMPABLE=0,      │ │
   │   │         oom_score_adj=-1000, SIGHUP ignored,         │ │
   │   │         mlockall (opt-in)                            │ │
   │   │  Win:   SetProcessMitigationPolicy(Dynamic|Image|    │ │
   │   │         ExtensionPoint), SetSecurityInfo (DACL)      │ │
   │   └──────────────────────────────────────────────────────┘ │
   │                                                            │
   │   ┌─────────┐    ┌─────────────┐    ┌──────────────────┐   │
   │   │ Backend │───▶│ Supply      │───▶│ score.c          │   │
   │   │ fanotify│    │ scanner     │    │ 7 identifiers    │   │
   │   │ inotify │    │  ┌───────┐  │    │ ┌─────────────┐  │   │
   │   │ rdc     │    │  │ scan_*│  │    │ │ I_LOC       │  │   │
   │   │ usn     │    │  │ + IoC │  │    │ │ I_NAME      │  │   │
   │   │ poll    │    │  │ tables│  │    │ │ I_COOC      │◀─┐ │   │
   │   │ (auto-  │    │  └───────┘  │    │ │ I_CONTEXT   │  │ │   │
   │   │  fallba │    │  capture +  │    │ │ I_PROV      │  │ │   │
   │   │  -ck)   │    │  re-stat -> │    │ │ I_FRESH     │  │ │   │
   │   │         │    │  I_VERIFY   │    │ │ I_VERIFY    │  │ │   │
   │   └─────────┘    └──────┬──────┘    │ └─────────────┘  │ │   │
   │        │                │           │  weighted 0..100 │ │   │
   │        │                ▼           │  threshold       │ │   │
   │        │         ┌─────────────┐    │  suppression     │ │   │
   │        │         │ forensics.c │    │  for SEV_WARN    │ │   │
   │        │         │ SHA-256 +   │    └────────┬─────────┘ │   │
   │        │         │ entropy +   │             │           │   │
   │        │         │ magic +     │             ▼           │   │
   │        │         │ lines +     │     ┌───────────────────┐│  │
   │        │         │ hits +      │     │  Events emitter   ││  │
   │        │         │ cooc        │────▶│  - JSONL sink     ││  │
   │        │         └─────────────┘     │  - syslog         ││  │
   │        │                             │  - alert hook     ││  │
   │        │                             │  (fork+execve)    ││  │
   │        │                             └───────┬───────────┘│  │
   │        │                                     │            │  │
   │        │                                     ▼            │  │
   │        │              ┌───────────────────────────────┐   │  │
   │        │              │  beige.c   BEIGE              │   │  │
   │        │              │  (Behavioral Event Intel.     │   │  │
   │        │              │   & Graph Engine)             │   │  │
   │        │              │  ┌─────────────────────────┐  │   │  │
   │        │              │  │ beige_ring.c    sliding │  │   │  │
   │        │              │  │   event window (4096)   │  │   │  │
   │        │              │  ├─────────────────────────┤  │   │  │
   │        │              │  │ beige_graph.c   path    │  │   │  │
   │        │              │  │   co-occurrence graph   │  │   │  │
   │        │              │  │   (1024 nodes, 4096 ed) │  │   │  │
   │        │              │  ├─────────────────────────┤  │   │  │
   │        │              │  │ beige_patterns.c   8    │  │   │  │
   │        │              │  │   campaign signatures   │  │   │  │
   │        │              │  ├─────────────────────────┤  │   │  │
   │        │              │  │ beige_stats.c   1-hour  │  │   │  │
   │        │              │  │   density + entropy +   │  │   │  │
   │        │              │  │   anomaly boost (0..40) │  │   │  │
   │        │              │  └─────────────────────────┘  │   │  │
   │        │              │  feeds I_COOC boost back ─────┼───┘  │
   │        │              │  to scoring next emit         │      │
   │        │              │  emits beige.campaign-detected│      │
   │        │              └────────────────┬──────────────┘      │
   │        │                               │                     │
   │        │                               ▼   (back to sink)    │
   │        │                                                     │
   │  ┌───────────────────────┐                                   │
   │  │ pkgmgr.c   PM binary  │                      │          │
   │  │ baseline + recovery   │──────────────────────┤          │
   │  │ (pm.binary-changed,   │                      │          │
   │  │  pm.binary-deleted,   │                      │          │
   │  │  pm.binary-restored,  │                      │          │
   │  │  pm.update-detected)  │                      │          │
   │  └───────────────────────┘                      │          │
   │  ┌───────────────────────┐                      │          │
   │  │ protect.c  curated    │                      │          │
   │  │ system regions per    │──────────────────────┤          │
   │  │ platform; semantic    │                      │          │
   │  │ event kinds:          │                      │          │
   │  │ system.pam-modified,  │                      │          │
   │  │ system.cron-modified, │                      │          │
   │  │ system.sudoers-...,   │                      │          │
   │  │ system.kext-..., etc. │                      │          │
   │  └───────────────────────┘                      │          │
   │  ┌───────────────────────┐                      │          │
   │  │ verify.c  -D / -V     │                      │          │
   │  │ baseline + diff       │                      │          │
   │  └───────────────────────┘                      │          │
   └─────────────────────────────────────────────────┼──────────┘
                                                     │
                                                     ▼
                                       ┌───────────────────────┐
                                       │ SIEM / Tauri GUI /    │
                                       │ on_alert hook /       │
                                       │ direcd -T / direcd -S │
                                       └───────────────────────┘

Every emitted event note ends with [score=N tier=LABEL verify=N], where score is the 7-identifier weighted confidence (0–100), tier is low|medium|high|critical, and verify is the at-emit-time re-stat agreement (100=identical, 70=mtime drift, 60=size drift, 30=inode swap or vanished). SEV_WARN events whose score falls below detection_threshold are suppressed (logged at INFO so an audit trail still exists); SEV_ERR events always fire.

Three Linux backends with automatic fallback.

  • fanotify — kernel-level, filesystem-wide. Requires CAP_SYS_ADMIN.
  • inotify — per-directory, recursive. No special privileges beyond read access to the watched paths. Watch-descriptor lifecycle is managed (auto-drop on IN_IGNORED / DELETE_SELF / MOVE_SELF, dedup on duplicate wd).
  • poll — pure POSIX lstat + SHA-256 + xattr-hash. Works anywhere as the last resort. The richest fingerprint, the only one that reliably catches xattr / file-capability injection, the only one that catches CVE-2026-31431-style page-cache tamper, and the one used by -D and -V. Hash table grows with load factor (no O(N²) cliff on big trees). Pinned PM binaries support absent → present recovery via pm.binary-restored.

Three Windows backends with the same fallback chain.

  • rdcReadDirectoryChangesW + IOCP. Bounds-checks every FILE_NOTIFY_INFORMATION record; fires a watch-silent warning if rearm fails.
  • usn — NTFS USN journal. Bounds-checks RecordLength / FileNameOffset / FileNameLength against the batch end.
  • poll.

Selection is automatic by default; backend = inotify (or -b inotify on the command line) forces a specific one.


Detection confidence: the 7-identifier scoring system

Every supply.* event runs through a small weighted-sum scorer implemented in src/score.c. Each identifier is a 0–100 unsigned value capturing one orthogonal source of evidence. The default weights sum to exactly 100 so the result is itself a 0–100 number.

Identifier Weight What it captures
I_NAME 20 Match strength: exact-IoC > prefix > substring > fuzzy/Levenshtein
I_VERIFY 18 Re-stat agreement at emit time (race-edit / TOCTOU detection)
I_COOC 18 Independent IoCs hitting the same file (1=30, 2=60, 3=80, 4+=100)
I_LOC 15 Path-tier (node_modules//etc high, /tmp lower)
I_CONTEXT 12 Extension × event-kind fit (eval in .js high, eval in .md low)
I_PROV 10 Provenance (PM-managed > OS-managed > user tree)
I_FRESH 7 mtime-age curve (just-modified = 100, > 30 days = 20)

Tiers are bucketed: 0–30 low, 31–50 medium, 51–70 high, 71–100 critical. detection_threshold (default 30) is the suppression cutoff for SEV_WARN events; SEV_ERR events always fire regardless. Severity is never raised by scoring, only suppression-eligible.

BEIGE: temporal correlation + campaign detection

Single events score in isolation. Real supply-chain compromises are multi-stage: a package.json mentions chalk @ a known-bad version; an index.js in the same package dumps a registry token; the same tree's .npmrc redirects the registry to an attacker mirror — three distinct file events that, taken together, are a campaign.

src/beige.c — Behavioral Event Intelligence & Graph Engine — keeps a 5-minute sliding window of every emitted event, builds a lightweight co-occurrence graph of package-root path prefixes, scores per-window density + path-entropy anomalies, and matches the cluster against eight hard-coded multi-stage signatures grounded in real 2022–2026 incidents (Shai-Hulud propagation, TeamPCP cascade, GlassWorm staging, Bybit SRI strip + supply hit, CanisterWorm wiper chain, etc.). When a match fires, BEIGE emits a beige.campaign-detected event AND boosts the I_COOC identifier of the next-scored event so that hits landing inside a detected campaign tier higher than the same hits in isolation.

Sub-module Role Bound
beige_ring.c Sliding-window event store (FIFO eviction) 4096 events
beige_graph.c Path-prefix co-occurrence graph (LRU eviction) 1024 nodes / 4096 edges
beige_patterns.c 8 built-in campaign signatures + external file loader 32 patterns × 8 stages
beige_stats.c 1-hour rolling density + path-entropy + 0..40 boost 12 buckets × 256 paths

Memory is bounded at init (~200 KiB total); no allocator failure path at runtime. If beige_init fails (OOM, unreasonable config), the daemon continues without correlation — fail-safe by design. --beige-dump prints the current window, graph, and stats. Full documentation in docs/BEIGE.md.

Verification system

For each scan, read_capped calls capture_verify(fd) which remembers (inode, size, mtime) of the buffer the scanner is about to read. At every emit_supply call the path is re-lstat'd and the result feeds I_VERIFY:

State at re-stat I_VERIFY
Identical (inode, size, mtime all match) 100
mtime changed only 70
size changed 60
Inode swap / file vanished 30
No capture (nested non-buffered scan) 50

forensic_save_t saves and restores the verify state across nested scans so scan_install_code calling scan_credentials doesn't clobber the parent's capture.

Protected regions

src/protect.c ships a curated registry of ~50 high-value system paths, tagged CRITICAL / SENSITIVE / NORMAL and platform-masked. When a fim.create or fim.modify event lands on one of these, an additional semantic event with the registry-defined kind is emitted at the appropriate severity:

  • Linux CRITICAL (raises SEV_ERR): /etc/pam.d, /etc/sudoers(.d), /etc/ssh/sshd_config(.d), /etc/passwd, /etc/shadow, /etc/security, /etc/systemd/{system,user}, /usr/lib/systemd/system, /etc/cron{tab,.d}, /var/spool/cron, /lib/modules, /etc/default/grub, /etc/grub.d
  • Linux SENSITIVE: profile.d, modprobe.d, sysctl.d, udev rules, /etc/hosts, /etc/resolv.conf, /etc/nsswitch.conf, CA cert bundles, firewall rules, polkit rules, init.d, rc.local
  • macOS: /Library/LaunchDaemons (CRITICAL), LaunchAgents, /Library/Extensions (kexts), /etc/sudoers, /etc/ssh/sshd_config
  • Windows: System32\Tasks, System32\drivers, System32\config (registry hives), Startup folder, GroupPolicy, drivers\etc\hosts

direcd --check (-K) lists every CRITICAL region present on the host that isn't under any configured watch = line and prints a final coverage summary; direcd --wizard prints all registry entries as watch = candidates (active or commented based on existing coverage) tagged with their tier and a one-line note.

Daemon hardening

harden.c applies in-process self-hardening early, before any subsystem touches the filesystem:

  • Linux: PR_SET_NO_NEW_PRIVS=1, PR_SET_DUMPABLE=0 (blocks ptrace by non-root, disables core dumps), oom_score_adj=-1000 (kernel never picks DireC as an OOM-victim; the systemd unit also sets this so the protection is in place even before harden_apply_self() runs), SIGHUP ignored, optional mlockall(MCL_CURRENT|MCL_FUTURE) via harden_lock_memory = yes.
  • Windows: SetProcessMitigationPolicy(DynamicCode | ImageLoad | ExtensionPointDisable) blocks runtime code injection, AppInit_DLLs, IME hooks, and remote-share DLL loads; SetSecurityInfo on the process handle restricts PROCESS_TERMINATE / VM_WRITE / VM_OPERATION / DUP_HANDLE to Local System and Administrators only; SetPriorityClass(HIGH_PRIORITY_CLASS). All Windows APIs resolved via GetProcAddress so Windows 7 builds gracefully degrade.

A harden status: … syslog line records exactly what got applied ("on", "n/a", or "FAILED" per identifier).

CVE-2026-31431 ("Copy Fail") awareness

The April-2026 Linux kernel flaw in the AF_ALG / authencesn in-place crypto path lets an unprivileged local user write 4 attacker-chosen bytes into the page cache of any readable file without going through write() — so mtime, size, and ctime stay unchanged. DireC catches this three ways:

  1. Pre-attack: --wizard and --check warn if the vulnerable algif_aead kernel module is loaded, with a concrete modprobe -r + blacklist mitigation.
  2. During staging: IoC strings (authencesn(, socket(AF_ALG, the literal CVE id, copy-fail) hit if the exploit script lands in a watched directory.
  3. Post-exploit: the poll backend fires system.page-cache-tamper (SEV_ERR) when fp_diff returns FP_DIFF_HASH alone with no other bits set — the unique filesystem footprint of a kernel-level page-cache write primitive. Requires paranoid = yes because the default fast-path skips re-hashing when stat is unchanged (which is exactly the state the CVE leaves behind).

Supported package managers

Kind Binary paths fingerprinted Lockfiles / manifests scanned
npm /usr/bin/npm, /usr/bin/npx, /usr/local/bin/npm, /usr/local/bin/npx, /opt/homebrew/bin/npm, /opt/homebrew/bin/npx package.json, package-lock.json, npm-shrinkwrap.json, yarn.lock, pnpm-lock.yaml, .npmrc
pip /usr/bin/pip, /usr/bin/pip3, /usr/local/bin/pip, /usr/local/bin/pip3, /opt/homebrew/bin/pip3 requirements.txt, Pipfile, Pipfile.lock, pyproject.toml, poetry.lock, uv.lock, setup.py, pip.conf, pip.ini
gem /usr/bin/gem, /usr/bin/bundle, /usr/local/bin/gem, /usr/local/bin/bundle Gemfile, Gemfile.lock, .gemrc
cargo /usr/bin/cargo, /usr/local/bin/cargo Cargo.toml, Cargo.lock, build.rs
go /usr/bin/go, /usr/local/bin/go, /usr/local/go/bin/go go.mod, go.sum
composer /usr/bin/composer, /usr/local/bin/composer composer.json (full scripts/lifecycle scan), composer.lock
rpm / dnf / yum /usr/bin/rpm, /usr/bin/dnf, /usr/bin/yum, /usr/bin/microdnf (no project lockfiles)
dpkg / apt /usr/bin/dpkg, /usr/bin/apt, /usr/bin/apt-get, /usr/bin/aptitude (no project lockfiles)
pacman /usr/bin/pacman (no project lockfiles)
brew /opt/homebrew/bin/brew, /usr/local/bin/brew (no project lockfiles)
snap /usr/bin/snap (no project lockfiles)
flatpak /usr/bin/flatpak (no project lockfiles)

Adding a new package manager is an append-only edit to src/pkgmgr.c; the rest of the daemon picks it up automatically.


Quickstart

One-command install (Linux)

sudo ./setup.sh

setup.sh builds, installs, runs direcd -K to validate the resulting config, and (if systemd is present) enables and starts the unit. It does NOT overwrite an existing /etc/direc/direc.conf, so it is safe to re-run after editing.

First-time setup walkthrough

sudo direcd --wizard

-W / --wizard runs a non-interactive first-time setup tour. It auto-detects your host's OS (Ubuntu/Debian/Arch/Fedora/Alpine on Linux, macOS by version, Windows 10/11 by build, BSDs via uname) — sourced from /etc/os-release + uname() on POSIX, from RtlGetVersion + GetNativeSystemInfo on Windows — and prints a configuration tailored to that specific OS. Arch's Python paths differ from Ubuntu's; macOS ships package managers under /opt/homebrew/...; Windows uses C:\Program Files\nodejs\...; the wizard knows.

It also detects which package managers are actually installed on this host (using the same pkgmgr_for_each_existing_bin path the daemon's pm_monitor uses), prints watch = lines targeting their state directories, and explains the highest-impact tunables (paranoid, pm_monitor, on_alert) so an operator new to DireC can choose intelligently rather than running on defaults that may or may not match their threat model. The output is plain stdout — copy what applies into /etc/direc/direc.conf, then validate with direcd --check (which itself shows the detected host on a Host: line).

The detected OS fingerprint is also embedded in every daemon.start event written to the JSONL sink, so the sink "remembers" which host each event came from across restarts and across kernel / distro upgrades:

{"ts":"2026-05-02T03:25:45Z","sev":"info","kind":"daemon.start",
 "note":"backend=poll paths=1 host=Ubuntu 24.04 LTS kernel=6.8.0
         arch=x86_64 init=systemd"}

Local telemetry (no network)

sudo direcd --stats

-S / --stats aggregates the JSONL events sink and prints counts by severity, by event kind (top 20), and by path (top 10). It is strictly local: it opens one file (the sink the daemon already writes to), produces output, and exits. No network calls, no telemetry export, no third-party dependencies — everything an operator needs to refine detection thresholds and find the noisy event kinds, without DireC ever leaving the host.

Live event tail

sudo direcd -T

-T follows the JSONL event sink and pretty-prints each line with colour-coded severity:

2026-05-02T01:23:45Z  WARN   supply.package          /home/dev/proj/package.json   chalk [5.6.1] (qix phishing wallet drainer)
2026-05-02T01:23:46Z  ERROR  pm.binary-changed       /usr/bin/npm                  pm=npm diff=content sha256=...

Color is auto-detected: enabled on a TTY (kitty, alacritty, foot, wezterm, gnome-terminal, iTerm2, Konsole, Terminal.app, Windows Terminal, PowerShell, conhost on Windows 10+), disabled when the output is piped or redirected. The detection honours NO_COLOR, FORCE_COLOR, and TERM=dumb, so it integrates cleanly with less, grep, jq, and CI logs that do or don't interpret ANSI. Tailing also survives log rotation: when the sink file is replaced (different inode) the new file is re-opened from its start.

Manual install (any POSIX)

# Build
make
sudo make install

# Edit config (see `Configuration` below)
sudoedit /etc/direc/direc.conf

# Validate the config before launching
sudo direcd -K -c /etc/direc/direc.conf

# Run
sudo systemctl daemon-reload
sudo systemctl enable --now direcd

# Watch detections live
sudo journalctl -fu direcd
sudo tail -f /var/log/direcd-events.jsonl | jq .

The bundled systemd unit ships with NoNewPrivileges, full ProtectSystem, MemoryDenyWriteExecute, a tight SystemCallFilter=@system-service, and a CapabilityBoundingSet of just CAP_SYS_ADMIN and CAP_DAC_READ_SEARCH (needed for fanotify and for traversing unreadable directories during the baseline scan).

The daemon binary is built with -D_FORTIFY_SOURCE=2, -fstack-protector-strong, -fstack-clash-protection, full RELRO, immediate binding (-z now), a non-executable stack, and PIE.

Windows

mingw32-make -f Makefile.win

Or cross-compile from Linux:

make -f Makefile.win CC=x86_64-w64-mingw32-gcc

Arch

cd packaging/arch
makepkg -si

Configuration

The defaults in conf/direc.conf are the recommended baseline. The fields most worth tuning:

# Which paths to watch. Add one `watch =` line per path.
watch = /etc
watch = /usr/bin
watch = /usr/sbin
watch = /boot
watch = /usr/lib/node_modules
watch = /usr/lib/python3/dist-packages

# Always rehash, never trust size+mtime. Recommended on hosts that
# hold production secrets or signing keys; mtime is trivially
# forgeable by any process that already wrote the file.
paranoid = yes

# Auto-baseline package-manager binaries. Catches "the package
# manager itself was tampered with" attacks. Symlinked PM paths
# (npm typically resolves to /usr/lib/node_modules/npm/bin/npm-cli.js)
# are followed; the readlink target is mixed into the content hash
# so a symlink swap also flips the fingerprint.
pm_monitor = yes

# JSONL event sink. The bundled GUI tails this; any SIEM / log
# shipper can ingest it.
events_jsonl = /var/log/direcd-events.jsonl

# Optional alert hook. Invoked via fork+execve (never /bin/sh) with
# argv: <severity> <kind> <path> <note>. DireC refuses to register a
# hook that isn't an absolute path, isn't a regular file, or is
# world-writable.
on_alert = /usr/local/sbin/direcd-alert.sh

# Site-specific IoC overrides (extra exfil domains, custom suspicious
# tokens). Format: `substring=...` or `domain=...`, one per line.
supply_iocs = /etc/direc/supply-iocs.conf

# Detection threshold (0-100). Every supply.* event runs through the
# 7-identifier weighted score; SEV_WARN events whose score falls
# below this threshold are suppressed (logged at INFO, not emitted).
# SEV_ERR events ALWAYS fire regardless. 0 = fire everything;
# 30 = default (suppresses prose / docs / test-fixture noise);
# 50 = moderate; 70 = strict.
detection_threshold = 30

# Pin the daemon's working set in RAM (Linux only; ignored elsewhere).
# Prevents an attacker with raw-disk access from reading fingerprints,
# IoC matches, or cached file content off the swap partition.
# Off by default because pinning all current+future pages can cause
# memory pressure on small (< 1 GiB RAM) hosts. Under systemd the
# unit ships LimitMEMLOCK=infinity so just turning this on works.
harden_lock_memory = no

Per-PM coverage details and the full lockfile / manifest table live in docs/package-managers.md. The lifecycle-by-lifecycle map of npm install to DireC events lives in docs/npm-protection.md.


Offline verification (-D / -V)

DireC can dump a fingerprint of every watched file and PM binary to JSONL on stdout, ship that out of band, and later verify any host against it. This is how you wire DireC into a CI gate or an out-of-band tamper check.

# 1. On a known-good host, dump a baseline of every watched file plus
#    every supported package-manager binary.
sudo direcd -c /etc/direc/direc.conf -D > golden.jsonl

# 2. Move golden.jsonl out of band. Sign it, store on a TPM, ship to
#    your build artefact registry, whatever.

# 3. Verify any host (now or later) against that baseline. Exit code
#    is 0 = clean, 1 = drift detected, 2 = I/O error.
sudo direcd -c /etc/direc/direc.conf -V golden.jsonl
echo $?

The dump format is line-oriented JSON whose schema matches the daemon's events_jsonl sink, so the same tooling (jq, grep, SIEM ingest) works for both modes.


Operational hardening already in place

  • All file reads use O_NOFOLLOW plus a S_ISREG check, so a hostile actor under a watched user-writable tree cannot redirect the daemon (running as root) into reading /etc/shadow, /proc/self/mem, a FIFO, or /dev/zero.
  • Trusted-symlink mode for hardcoded PM-binary paths only: the fp_compute_trusted variant follows the symlink to the real npm-cli.js (or equivalent), but mixes the readlink target into the content hash so a symlink swap is itself tamper-evident.
  • The pidfile and the JSONL events file are opened with O_NOFOLLOW and restrictive permissions (0644 / 0640).
  • Recursive directory walks are bounded so a poisoned filesystem layout cannot blow the stack.
  • inotify watches use IN_DONT_FOLLOW so a symlink planted at a watched name cannot redirect the watch elsewhere.
  • The JSONL emitter escapes every byte ≥ 0x80 as \u00xx, so a path containing invalid UTF-8 still produces strictly valid JSON.
  • The supply-chain scanner's read is sized to the actual file (cap 1 MiB), so a giant minified bundle cannot stall the daemon and a 100-byte package.json doesn't pull a 1 MiB allocation.
  • Watch paths from direc.conf must be absolute; relative or empty paths are rejected with a stderr warning, never silently resolved against chdir("/").
  • The poll interval is range-checked (1 .. 86400 s) so a negative or garbage value cannot turn the loop into a busy-spin.
  • SIGCHLD is configured SA_NOCLDWAIT, and hooks_dispatch drains zombies on each call, so a flood of alerts cannot exhaust the process table.
  • The systemd unit drops every capability except those genuinely needed and applies a @system-service syscall filter.

Repository layout

src/                   daemon (C11, no third-party runtime deps)
  main.c               POSIX entry point, signals, daemonize, pidfile,
                       --check / --tail / --stats / --wizard /
                       --supervisor dispatch, CVE-2026-31431 +
                       protect-region coverage warnings
  main_win.c           Windows entry point
  config.c             direc.conf parser; bounded ints, abs-path
                       check, line-truncation detection, empty-value
                       rejection, detection_threshold +
                       harden_lock_memory parsing
  backend.c            backend selection + dispatch
  backends/            fanotify, inotify, posix_poll (with hash-table
                       resize, page-cache-tamper detection,
                       pm.update-detected burst summary, pm.binary-
                       restored), win_rdc, win_usn, win_poll
  fingerprint.c        SHA-256 + size + mode + uid/gid + xattr-hash
                       (with alloc-failure propagation so a partial
                       fingerprint never gets recorded as
                       "no xattrs"); trusted-symlink variant for PM
                       binaries; lstat.st_size disambiguation for
                       readlink truncation
  forensics.c          post-detection profiler: SHA-256 + size +
                       lines + Shannon entropy + magic-bytes hex,
                       paired with each supply.* event so an IR
                       responder gets one structured fingerprint
                       per file investigated
  harden.c             Linux: PR_SET_NO_NEW_PRIVS / DUMPABLE / oom_
                       score_adj / SIGHUP / mlockall.
                       Windows: SetProcessMitigationPolicy + process
                       DACL.
                       Cross-platform --supervisor (POSIX
                       fork+execvp, Windows CreateProcess) with
                       capped exponential backoff
  hooks.c              fork+execve alert hook (no shell), zombie reaper
  events.c             JSONL event sink with strict-UTF-8 escaping;
                       vsnprintf truncation marker; fclose-error log
  iocs.c               curated IoC database (packages, popular names,
                       suspicious substrings, OOB domains, lifecycle
                       keys, credential prefixes, bidi sequences,
                       .pth payload tokens, CVE-2026-31431 exploit
                       signatures)
  pkgmgr.c             package-manager registry + symlink-aware
                       known-bin lookup; pkgmgr_state_kind_for_path
                       (used by pm.update-detected)
  protect.c            curated registry of ~50 high-value system
                       regions per platform (PAM, sshd, sudoers,
                       systemd, cron, kernel modules, udev, GRUB,
                       LaunchDaemons, kexts, Tasks, drivers, registry
                       hives, ...); each entry has a semantic event
                       kind + tier
  score.c              7-identifier confidence-weighted scorer:
                       I_LOC, I_NAME, I_COOC, I_CONTEXT, I_PROV,
                       I_FRESH, I_VERIFY -> 0..100 score + tier
                       label; threshold suppression for SEV_WARN
  sha256.c             portable SHA-256
  supply.c             supply-chain scanner: lockfile / setup.py /
                       build.rs / .npmrc / package.json / .pth /
                       workflow / cross-ecosystem / typosquat /
                       credential / OOB-domain / bidi /
                       unicode-injection / SRI-gap / Cargo /
                       Gemfile / pip.conf / composer / MCP /
                       AI-bypass / self-hosted-runner /
                       PRT-secrets-leak / remote-dynamic-dependency.
                       Stat-capture for I_VERIFY; per-emit scoring
                       and threshold suppression; AWS-EXAMPLE +
                       FAKEACCESS dummy-credential FP suppression
  verify.c             -D / -V offline baseline + compare; bounded
                       json_str_field cap to prevent integer-overflow
                       heap overflow on a malicious baseline
  osinfo.c             cross-platform host detection (Linux distro,
                       macOS version, Windows 10/11 by build, BSDs)
  stats.c              local JSONL aggregator (-S); strictly local,
                       no network
  tail.c               -T pretty-printed event tail with colour;
                       NO_COLOR / FORCE_COLOR / TERM=dumb honoured
  term.c               cross-platform terminal-palette autodetect
                       (kitty / iTerm2 / Windows Terminal /
                       PowerShell / conhost on Win10+)
  wizard.c             --wizard first-time setup; OS-aware watch
                       recommendations + critical-region section +
                       CVE-2026-31431 mitigation guidance
  xmem.c               xmalloc / xrealloc / xstrdup
  beige.c              BEIGE Behavioral Event Intelligence & Graph
                       Engine: bounded sliding-window of every
                       emitted event, lightweight path-prefix co-
                       occurrence graph, multi-stage campaign
                       signature matcher, anomaly-density boost.
                       All memory bounded at init (~200 KiB);
                       fail-safe init disables BEIGE on OOM
                       without taking the daemon down. Feeds the
                       I_COOC scoring identifier.
  beige_ring.c         Bounded circular event store (4096 events,
                       FIFO eviction). Foundation data structure
                       every other BEIGE module reads from
  beige_graph.c        Path-prefix co-occurrence graph: 1024 nodes,
                       4096 directed edges, LRU-by-timestamp
                       eviction, periodic decay sweep
  beige_patterns.c     Eight hard-coded campaign signatures +
                       optional external pattern-file loader; up to
                       32 patterns × 8 stages each
  beige_stats.c        Rolling 1-hour window split into 12 five-
                       minute buckets; density + log2 path-entropy
                       => 0..40 boost folded into I_COOC
gui/                   Tauri 2 + React + TypeScript read-only observer
                       UI; tails events_jsonl in real time
tests/                 Standalone unit tests for the BEIGE modules
                       (ring, graph, patterns, stats, core API,
                       integration, validation, safe_strlen).
                       `make -C tests run` is part of CI
conf/                  direc.conf and supply-iocs.conf templates
systemd/               sandboxed direcd.service unit (Restart=always,
                       OOMScoreAdjust=-1000, LimitMEMLOCK=infinity,
                       full @system-service syscall filter)
packaging/arch/        PKGBUILD for Arch Linux
docs/                  ecosystem references, npm install protection
                       map, per-incident write-ups, supported PM
                       table, BEIGE.md (full BEIGE design + operator
                       playbook)
YurilLAB Supply Chain.docx   YurilLAB research, 2022-2026

Roadmap

The work below is sequenced from the YurilLAB DireC Implementation Document (May 2026), which maps documented 2022–2026 supply-chain incidents to concrete DireC detections. Each item is grounded in a specific incident; see docs/roadmap.md for the full incident-to-detection map.

Near-term (architectural extensions, no external dependencies).

  • Slopsquatting Bloom-filter v1 (USENIX Security 2025 hallucination corpus, ~205,474 names, ~300 KB filter at 1% FPR).
  • Maintainer behavioural anomaly detection (registry API polling, ctx hijack and XZ-Utils trust-building shape).
  • Tag rollback / retroactive tag poisoning (GITREF backend watching .git/refs/tags/; tj-actions / TeamPCP imposter commits).

Medium-term (new parsing capabilities).

  • Phantom dependency detection (Axios plain-crypto-js, Mar 2026): cross-reference packages in node_modules/ and the lockfile against require() / import statements in the application source tree.
  • Nested .pth encoding decoder (TeamPCP litellm_init.pth, Mar 2026): iterative base64 / hex / ROT13 unwrap before applying the existing ioc_pth_payload_tokens[] matching.
  • Build-to-source divergence (XZ-Utils CVE-2024-3094; SolarWinds SUNBURST): extend direcd -V with a --build-verify mode that diffs build scripts, M4 macros, test-fixture binaries, and shipped shared objects between a Git checkout and a release artefact.

Research (high-effort, narrow blast radius).

  • ELF IFUNC resolver hijacking (XZ-Utils CVE-2024-3094 binary layer): lightweight in-process ELF parser, IFUNC symbol classification, known-benign IFUNC resolver allow-list.
  • Per-PM known-good-version fingerprint database, so pm.binary-changed can be down-graded to INFO when the new content hash matches a published upstream release.
  • Cross-ecosystem coverage: .NET extension method interception (shanhai666 NuGet PLC sabotage, 2023–2024), Java classpath shadowing (MavenGate, 2024), Go module-proxy cache poisoning (boltdb-go/bolt, 2021–2024).

Contributing

DireC's IoC database is the easiest meaningful contribution: append a new entry to src/iocs.c (ioc_packages[], ioc_oob_domains[], ioc_suspicious_substr[]) with a short reference, run the e2e tests in .github/workflows/ci.yml locally, and open a PR.

For new detection classes, the per-detection layout in supply.c keeps additions self-contained — see scan_setup_py, scan_npmrc, or the .pth payload scan block as templates.


Reporting an incident

If DireC catches something we should know about, please open an issue or contact the YurilLAB Security Team. The research document is updated as new incidents are publicly disclosed.


License

See the in-tree LICENSE file once published. Until then, all code is provided for defensive supply-chain monitoring use only.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors