Caution
This repository is ARCHIVED. Active development, maintenance, and new security patches have stopped.
The existing security gate/approval system (dl_load:gate/*, core/security/cmd_gate, the check_all chain) has not been removed and continues to run, but it will not be updated against new threats.
Datapack risks (also apply to this project):
- String injection:
$$(...)macro calls run without type safety in the datapack environment. - No namespace isolation: one datapack can freely interfere with another datapack's function/data space.
- TOCTOU issues: the tick gap between check and use can be exploited.
- No compile-time type checking: errors only surface at runtime (or not at all).
Recommended path: migrate to a Fabric mod under runtoolkit targeting this functionality — MC 1.21.1 (upgrade to 1.21.11 if needed). Fabric provides type safety and real namespace isolation, which structurally removes most of these risks.
Minecraft Java Edition 26.3-snapshot-5 | Multiplayer-Safe | Pure Datapack
Current version: v6.0.1-pre2 (ARCHIVED)
Warning
This repository is archived. It will no longer receive security improvements, bug fixes, or new features. It can still be used "as-is"; use in production environments is not recommended.
Do not copy datalib:input or datalib:engine into your own datapack. It is an internal implementation detail and may change without notice between releases.
Note
/reload is no longer required. dataLib initializes automatically, and player-targeted commands (such as tellraw @s) are executed when the first player joins the world.
Note
v6.0.1-pre2: the stage-0 load entry point was renamed dl_load:_ → dl_load:main, the marker-entity + say broadcast pattern was replaced everywhere with tellraw (using the current click_event/command button format — the old clickEvent/value field names were renamed by Mojang as of 1.21.5), and the load pipeline was split into dl_load:resolve/* (version + dependency resolution) and dl_load:loader/* (scoreboard/storage initialization).
🛡️ This is a Minecraft Datapack — it contains no executables or scripts outside of
.mcfunctionfiles. Some antivirus software may flag.mcfunctionfiles as suspicious due to macro-like syntax. This is a false positive. The pack has been scanned on VirusTotal and returned clean. Only download from this official repository. Do not trust redistributed or repackaged versions from third-party sources.
-
Place dataLib-full.zip into /datapacks/
-
Add the following logic to your datapack's initialization files:
#> <namespace>:load
execute unless data storage datalib:engine {global:{loaded:1b}} run function <namespace>:load_datalib#> <namespace>:load_datalib
execute if data storage <namespace>:engine {loaded_datalib:1b} run return 0
function dl_load:load/yes
function dl_load:load/fork_no
data modify storage <namespace>:engine loaded_datalib set value 1bdatalib:engine (persistent data)
├── global
│ ├── version: "v6.0.1-pre2"
│ └── tick: <int>
├── players
│ └── Steve { coins:150, level:5, xp:2300, online:1b, ... }
├── queue
│ └── [{func:"mypack:event/end", delay:100}]
├── cooldowns
│ └── Steve { fireball: 2460, dash: 1870 } ← expiry ticks
└── events
└── on_join: [{func:"mypack:welcome"}, {func:"mypack:xp_bonus"}]
datalib:input (sending data to a function)
datalib:output (receiving results from a function)
Used with execute if predicate <id>.
| Predicate | Description |
|---|---|
datalib:is_survival |
Player is in survival mode |
datalib:is_creative |
Player is in creative mode |
datalib:has_empty_mainhand |
Main hand is empty |
datalib:is_full_health |
Player is at full health (20 HP) |
datalib:is_sneaking |
Player is sneaking |
datalib:is_sprinting |
Player is sprinting |
datalib:is_burning |
Player is on fire |
datalib:is_on_ground |
Player is on the ground |
datalib:is_daytime |
Daytime (0–12000 ticks) |
datalib:is_raining |
It is raining |
datalib:is_thundering |
There is a thunderstorm |
datalib:in_overworld |
Player is in the Overworld |
datalib:in_nether |
Player is in the Nether |
datalib:in_end |
Player is in the End |
datalib:is_hungry |
Food level is below 20 (datalib.food score) |
datalib:health_below_half |
Health is at or below 10 HP (datalib.health score) |
datalib:is_holding_sword |
Main hand holds any item in #minecraft:swords |
datalib:is_flying |
Player is flying (creative/spectator flight or elytra without gliding) |
datalib:is_in_water |
Player's location fluid tag is #minecraft:water |
datalib:weather_clear |
Not raining and not thundering |
Full reference: Predicate Reference
Repository: https://github.com/LanternMC/load
License: BSD 0-Clause (public domain)
Provides deterministic load order, version tracking, and pre/load/post-load hooks.
# Check if dataLib is loaded
execute if score #dataLib load.status matches 1.. run say dataLib is loaded
# Get version (major*10000 + minor*100 + patch → v6.0.1 = 601)
scoreboard players get dataLib load.statusRepository: https://github.com/CMDred/StringLib
License: MIT
Bundled under the stringlib namespace. Exposed via datalib:core/lib/string/*.
| Function | Description |
|---|---|
lib/string/concat |
Join a string array |
lib/string/find |
Find substring index |
lib/string/replace |
Replace substring |
lib/string/split |
Split by separator |
lib/string/insert |
Insert at index |
lib/string/to_lowercase |
Lowercase (A–Z, fast) |
lib/string/to_uppercase |
Uppercase (a–z, fast) |
lib/string/to_number |
String → numeric NBT |
lib/string/to_string |
Value → string |
All functions read from datalib:input and write to datalib:output string.result.
data modify storage datalib:input string set value "Hello World"
data modify storage datalib:input find set value "World"
data modify storage datalib:input replace set value "Everyone"
function datalib:core/lib/string/replace
# datalib:output string.result → "Hello Everyone"Per-player permission tags, gated behind admin status. Admins (datalib.admin tag) can grant/revoke arbitrary permission strings; a denied check plays a sound and shows the player a "no permission" message.
$data modify storage datalib:input perm set value {player:"Steve",perm:"build"}
function datalib:api/perm/grant with storage datalib:input perm
# grants tag perm.build to Steve, records advancement datalib:api/perm/build
$data modify storage datalib:input perm set value {player:"Steve",perm:"build"}
function datalib:api/perm/check with storage datalib:input perm
# returns 1 if Steve is admin OR holds tag perm.build, else 0 (and notifies the player)Related: perm/revoke, perm/has, perm/list, perm/clear, plus perm/trigger/* for binding permissions to /trigger commands.
Not a vulnerability in dataLib or in Minecraft/Log4j — this is a config pitfall to avoid if any part of your setup uses minecraft:test_block.
minecraft:test_block[mode=log] is designed to write its message field to the log on every redstone trigger. If a test_block in mode=log is wired to a constant power source (a redstone_block, or a repeating trigger inside a minecraft:tick function), it will write that line to the log continuously — every tick if triggered from tick. This is expected behavior of the block, not a bug, but repeated/automated triggering can grow log files quickly and add disk/IO load on a server.
This has no relation to Log4j or any CVE — it's a plain log call (LOGGER.info(message)), not a lookup/JNDI-style resolution path.
If you use test_block anywhere in a datapack:
- Don't wire
mode=logtest_blocks tominecraft:tickor any always-on power source. - Prefer
mode=accept/mode=failfor actual gametest logic, and reservemode=logfor one-off manual debugging. - If you inherited a world with one already spamming, break the redstone connection or
/setblockover it to remove it.
A separate, numeric permission tier (dl.perm_level scoreboard, 0–4) that gates access to command-execution features such as cb/run — independent from the tag-based Permission System above.
| Level | Meaning |
|---|---|
| 0 | No access (default for new players) |
| 1 | Basic — cmd/ functions requiring admin_min_level=1 |
| 2 | Standard — full cmd/ access (default) |
| 3 | Elevated — may trigger $$(cmd) execution |
| 4 | Super — may trigger $$(cmd) even in sandbox mode |
$function datalib:api/security/set_level {player:"Steve",level:3}
$function datalib:api/security/get_level {player:"Steve"}Note: if
admin_can_overrideis0b, even level-4 players are still capped by thesecurity.sandbox_cmd_min_levelconfig floor.
Runs an arbitrary command string through a temporary command block instead of execute run, with optional scheduling. Gated by the Security Levels system above (security.cmd_min_level).
# Immediate — fires next tick, cleaned up 2 ticks later
data modify storage datalib:input cb set value {cmd:"say hello"}
function datalib:api/cb/run
# Delayed — pushes onto an internal delay queue
data modify storage datalib:input cb set value {cmd:"say delayed!",delay:40}
function datalib:api/cb/run_delayedx/y/z in the input default to 0 -64 0. Related: cb/run_sequence, cb/cancel, cb/list, cb/queue_size.
These methods do not merge dataLib into your source datapack. Your original datapack folder is left untouched. Each method reads a target datapack path, copies datalib in alongside it, patches a generated hook into <namespace>:load, and writes the result as a new, separate zip next to your original — producing two outputs:
dataLib-full.zip— dataLib alone, unchanged (from./gradlew zipFull)<target>-injected.zip— your datapack + dataLib + the load hook, packaged together
None of the four methods below write to your source files. They only read from datapacks/dataLib (or dataLib-full.zip) and the target path you give them, and write to an output directory.
Add this as a reusable workflow (.github/workflows/inject.yml) in your own datapack repo, or call it via workflow_call from runtoolkit/dataLib-dp. It checks out both repos, copies datalib into a scratch copy of your pack, patches the hook, and uploads the result as a build artifact — your repo's tracked files are never modified or committed back.
name: Inject dataLib
on:
workflow_dispatch:
inputs:
target_namespace:
description: "Your datapack's namespace (e.g. mypack)"
required: true
datalib_ref:
description: "dataLib-dp ref/tag to inject"
default: "main"
jobs:
inject:
runs-on: ubuntu-latest
steps:
- name: Checkout target datapack
uses: actions/checkout@v6
with:
path: target
- name: Checkout dataLib-dp
uses: actions/checkout@v6
with:
repository: runtoolkit/dataLib-dp
ref: ${{ inputs.datalib_ref }}
path: datalib-src
- name: Build dataLib-full.zip
working-directory: datalib-src
run: |
chmod +x gradlew
./gradlew zipFull --no-daemon
- name: Assemble injected copy (scratch dir, source untouched)
env:
NS: ${{ inputs.target_namespace }}
run: |
set -euo pipefail
mkdir -p out/injected
cp -r target/. out/injected/
mkdir -p tmp_datalib
unzip -q datalib-src/build/dist/dataLib-full.zip -d tmp_datalib
cp -r tmp_datalib/data/datalib out/injected/data/
cp -r tmp_datalib/data/dl_load out/injected/data/
cp -r tmp_datalib/data/stringlib out/injected/data/
cp -r tmp_datalib/data/datalib.main out/injected/data/
cp -r tmp_datalib/data/stringlib out/injected/data/
LOAD_FILE="out/injected/data/${NS}/function/load.mcfunction"
mkdir -p "$(dirname "$LOAD_FILE")"
if ! grep -q "loaded_datalib" "$LOAD_FILE" 2>/dev/null; then
cat <<EOF >> "$LOAD_FILE"
execute unless data storage datalib:engine {global:{loaded:1b}} run function ${NS}:load_datalib
EOF
fi
cat <<EOF > "out/injected/data/${NS}/function/load_datalib.mcfunction"
execute if data storage ${NS}:engine {loaded_datalib:1b} run return 0
function dl_load:load/yes
function dl_load:load/fork_no
data modify storage ${NS}:engine loaded_datalib set value 1b
EOF
- name: Zip injected pack
run: cd out/injected && zip -qr ../../${{ inputs.target_namespace }}-injected.zip .
- uses: actions/upload-artifact@v7
with:
name: dataLib-full
path: datalib-src/build/dist/dataLib-full.zip
if-no-files-found: error
- uses: actions/upload-artifact@v7
with:
name: ${{ inputs.target_namespace }}-injected
path: ${{ inputs.target_namespace }}-injected.zip
if-no-files-found: errorTwo artifacts are produced per run: the plain dataLib-full.zip and <namespace>-injected.zip. Neither is committed to any repo.
#!/usr/bin/env bash
# inject-datalib.sh
# Usage: ./inject-datalib.sh <path-to-target-datapack> <target-namespace> [dataLib-full.zip]
set -euo pipefail
TARGET_DIR="${1:?target datapack path required}"
NAMESPACE="${2:?target namespace required}"
DATALIB_ZIP="${3:-dataLib-full.zip}"
if [ ! -d "$TARGET_DIR" ]; then
echo "error: target datapack not found: $TARGET_DIR" >&2
exit 1
fi
# If dataLib-full.zip is missing, clone dataLib-dp and build it.
if [ ! -f "$DATALIB_ZIP" ]; then
echo "warning: $DATALIB_ZIP not found. Cloning and building dataLib-dp..." >&2
BUILD_DIR="$(mktemp -d)"
git clone https://github.com/runtoolkit/dataLib-dp.git "$BUILD_DIR/dataLib-dp"
(
cd "$BUILD_DIR/dataLib-dp"
chmod +x ./gradlew
./gradlew zipFull
)
# Locate the produced zip (adjust glob if the actual output path differs).
BUILT_ZIP="$(find "$BUILD_DIR/dataLib-dp" -maxdepth 6 -iname 'dataLib-full*.zip' -print -quit)"
if [ -z "$BUILT_ZIP" ]; then
echo "error: zipFull ran but no dataLib-full*.zip was found under build output." >&2
rm -rf "$BUILD_DIR"
exit 1
fi
cp "$BUILT_ZIP" "$DATALIB_ZIP"
rm -rf "$BUILD_DIR"
fi
OUT_DIR="$(mktemp -d)"
trap 'rm -rf "$OUT_DIR"' EXIT
# Copy target into scratch — source datapack is never touched.
cp -r "$TARGET_DIR/." "$OUT_DIR/"
# Unpack dataLib and copy only its known data folders in.
UNZIP_DIR="$(mktemp -d)"
unzip -q "$DATALIB_ZIP" -d "$UNZIP_DIR"
mkdir -p "$OUT_DIR/data"
for module in datalib datalib.main dl_load player_action stringlib; do
SRC="$UNZIP_DIR/data/$module"
if [ ! -d "$SRC" ]; then
echo "error: expected module missing from zip: data/$module" >&2
rm -rf "$UNZIP_DIR"
exit 1
fi
cp -r "$SRC" "$OUT_DIR/data/"
done
rm -rf "$UNZIP_DIR"
# Patch the load hook.
LOAD_FILE="$OUT_DIR/data/$NAMESPACE/function/load.mcfunction"
mkdir -p "$(dirname "$LOAD_FILE")"
touch "$LOAD_FILE"
if ! grep -q "loaded_datalib" "$LOAD_FILE"; then
{
echo ""
echo "execute unless data storage datalib:engine {global:{loaded:1b}} run function ${NAMESPACE}:load_datalib"
} >> "$LOAD_FILE"
fi
cat > "$OUT_DIR/data/$NAMESPACE/function/load_datalib.mcfunction" <<EOF
execute if data storage ${NAMESPACE}:engine {loaded_datalib:1b} run return 0
function dl_load:load/yes
function dl_load:load/fork_no
data modify storage ${NAMESPACE}:engine loaded_datalib set value 1b
EOF
OUT_ZIP="$(basename "$TARGET_DIR")-injected.zip"
( cd "$OUT_DIR" && zip -qr "$OLDPWD/$OUT_ZIP" . )
echo "Wrote $OUT_ZIP ($TARGET_DIR was not modified)"dataLib v6.0.1 | MC Java 26.3-snapshot-5 | Pure Datapack