diff --git a/HISTORY.md b/HISTORY.md
index 0c15f042d..bd3fa7a89 100755
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -1,6 +1,11 @@
# Snap! (BYOB) History
## in development:
+* **Notable Fixes:**
+ * disabled abusing the "primitive" block with metaprogramming to run arbitrary JS code. Sigh.
+
+### 2024-09-13
+* byob, objects, threads, store: disabled abusing the "primitive" block with metaprogramming to run arbitrary JS code. Sigh.
## 10.0.7:
* **Translation Updates:**
diff --git a/snap.html b/snap.html
index da57ad51d..0a2450a63 100755
--- a/snap.html
+++ b/snap.html
@@ -17,20 +17,20 @@
-
-
+
+
-
+
-
+
diff --git a/src/byob.js b/src/byob.js
index 6e3c004bf..99c506f3c 100644
--- a/src/byob.js
+++ b/src/byob.js
@@ -111,7 +111,7 @@ ArgLabelMorph, embedMetadataPNG, ArgMorph, RingMorph*/
// Global stuff ////////////////////////////////////////////////////////
-modules.byob = '2024-August-09';
+modules.byob = '2024-September-13';
// Declarations
@@ -661,6 +661,15 @@ CustomBlockDefinition.prototype.isDirectlyRecursive = function () {
return this.cachedIsRecursive;
};
+CustomBlockDefinition.prototype.setPrimitive = function (prim) {
+ if (isString(prim) &&
+ !Object.keys(SpriteMorph.prototype.blocks).includes(prim)) {
+ // console.warn('attempted to set unlisted primitive:', prim);
+ return;
+ }
+ this.primitive = prim;
+};
+
// CustomBlockDefinition localizing
CustomBlockDefinition.prototype.localizedSpec = function () {
@@ -806,7 +815,7 @@ CustomBlockDefinition.prototype.setBlockDefinition = function (aContext) {
if (body.expression?.selector === 'doPrimitive' &&
body.expression.inputs()[0].value
) {
- this.primitive = body.expression.inputs()[1].contents().text || null;
+ this.setPrimitive(body.expression.inputs()[1].contents().text || null);
} else {
this.primitive = null;
}
@@ -3411,9 +3420,9 @@ BlockEditorMorph.prototype.updateDefinition = function () {
if (this.definition.body?.expression?.selector === 'doPrimitive' &&
this.definition.body.expression.inputs()[0].value
) {
- this.definition.primitive =
- this.definition.body.expression.inputs()[1].contents().text
- || null;
+ this.definition.setPrimitive(
+ this.definition.body.expression.inputs()[1].contents().text || null
+ );
} else {
this.definition.primitive = null;
}
diff --git a/src/objects.js b/src/objects.js
index df6c3b80e..06cea5d5c 100644
--- a/src/objects.js
+++ b/src/objects.js
@@ -96,7 +96,7 @@ CustomBlockDefinition, exportEmbroidery*/
/*jshint esversion: 11*/
-modules.objects = '2024-August-20';
+modules.objects = '2024-September-13';
var SpriteMorph;
var StageMorph;
@@ -2482,8 +2482,9 @@ SpriteMorph.prototype.toggleAllCustomizedPrimitives = function (stage, choice) {
var prim = def.body?.expression;
if (prim && prim.selector === 'doPrimitive' && prim.nextBlock()) {
prim.inputs()[0].setContents(choice);
- def.primitive = choice ? prim.inputs()[1].contents().text || null
- : null;
+ def.setPrimitive(
+ choice ? prim.inputs()[1].contents().text || null : null
+ );
stage.allBlockInstances(def).reverse().forEach(block =>
block.selector = def.primitive || 'evaluateCustomBlock'
);
diff --git a/src/store.js b/src/store.js
index 6cc3538b2..e9244b8fe 100644
--- a/src/store.js
+++ b/src/store.js
@@ -63,7 +63,7 @@ Project*/
// Global stuff ////////////////////////////////////////////////////////
-modules.store = '2024-July-24';
+modules.store = '2024-September-13';
// XML_Serializer ///////////////////////////////////////////////////////
/*
@@ -1052,7 +1052,7 @@ SnapSerializer.prototype.loadCustomBlocks = function (
}
definition.type = child.attributes.type || 'command';
definition.selector = child.attributes.selector || null;
- definition.primitive = child.attributes.primitive || null;
+ definition.setPrimitive(child.attributes.primitive || null);
definition.isHelper = (child.attributes.helper === 'true') || false;
definition.spaceAbove = (child.attributes.space === 'true') || false;
definition.isGlobal = (isGlobal === true);
@@ -1174,7 +1174,7 @@ SnapSerializer.prototype.loadCustomizedPrimitives = function (
}
definition.type = child.attributes.type || 'command';
definition.selector = sel || null;
- definition.primitive = child.attributes.primitive || null;
+ definition.setPrimitive(child.attributes.primitive || null);
definition.isHelper = (child.attributes.helper === 'true') || false;
definition.isGlobal = true;
diff --git a/src/threads.js b/src/threads.js
index f27969b43..07747503c 100644
--- a/src/threads.js
+++ b/src/threads.js
@@ -65,7 +65,7 @@ StagePickerMorph, CustomBlockDefinition, CommentMorph*/
/*jshint esversion: 11, bitwise: false, evil: true*/
-modules.threads = '2024-August-12';
+modules.threads = '2024-September-13';
var ThreadManager;
var Process;
@@ -8431,8 +8431,9 @@ Process.prototype.doSetBlockAttribute = function (attribute, block, val) {
val = [true, 1, '1'].includes(val);
if (prim && prim.selector === 'doPrimitive' && prim.nextBlock()) {
prim.inputs()[0].setContents(val);
- def.primitive = val ? prim.inputs()[1].contents().text || null
- : null;
+ def.setPrimitive(
+ val ? prim.inputs()[1].contents().text || null : null
+ );
}
break;
case 'category':