diff --git a/frontend/index.html b/frontend/index.html
index 569a8ac..4a49416 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -1682,7 +1682,7 @@
// ===== Version =====
// Single source of truth for the PWA version: shown in the header and used
// by the server version check in pysimConnect().
-const SIMPLE_VERSION = '3.6.14';
+const SIMPLE_VERSION = '3.6.15';
document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION;
// ===== Tab switching =====
@@ -4194,7 +4194,9 @@ function chainRamBuildRowHex(idx, row) {
var delMode = f.delMode || '00';
var df7 = '4F' + berLenStr(aid.length / 2) + aid;
var dl7 = berLenStr(df7.length / 2);
- return buildApdu(0x80, 0xE4, 0x00, parseInt(delMode, 16), parseInt(dl7, 16), df7) + '00';
+ // case 3 (no Le), like the server's ramDeleteApdu: a trailing Le byte
+ // becomes a phantom command on the card's SCP80 layer (SW 6700).
+ return buildApdu(0x80, 0xE4, 0x00, parseInt(delMode, 16), parseInt(dl7, 16), df7);
}
if (cmd === 'get-status') {
var gsMode = f.gsMode || '80';
@@ -8896,15 +8898,17 @@ async function ramExplore(sp) {
// GP DELETE (Card Spec v2.3.1 11.2, Tables 11-20/11-23): P1=00 (last/only
// command), P2.b8 selects "object" ('00') or "object and related objects"
-// ('80'), the data field carries the mandatory '4F' AID TLV and Le is '00'.
-// The old inline builder called an undefined length helper (`_ber_len`): a
-// ReferenceError
-// after the confirm - the Delete buttons never sent anything) and omitted Le.
+// ('80'), the data field carries the mandatory '4F' AID TLV. The APDU is
+// case 3 (no Le): a trailing Le byte makes the card's SCP80 layer count a
+// phantom second command whose SW 6700 masks the real result (live
+// 2026-09-28) - same as INSTALL/LOAD since v3.6.8. The old inline builder
+// called an undefined length helper (`_ber_len`): a ReferenceError after the
+// confirm - the Delete buttons never sent anything.
function ramDeleteApdu(aid, withCascade) {
const a = (aid || '').replace(/[^0-9a-fA-F]/g, '').toUpperCase();
if (!a) return '';
const data = '4F' + berLenStr(a.length / 2) + a;
- return '80E400' + (withCascade ? '80' : '00') + berLenStr(data.length / 2) + data + '00';
+ return '80E400' + (withCascade ? '80' : '00') + berLenStr(data.length / 2) + data;
}
// A successful delete drops the object from the Explore result locally (the
diff --git a/frontend/sw.js b/frontend/sw.js
index fb27eff..f21c44f 100644
--- a/frontend/sw.js
+++ b/frontend/sw.js
@@ -1,4 +1,4 @@
-const CACHE = 'simple-v287';
+const CACHE = 'simple-v288';
const URLS = [
'index.html',
'help.html',
diff --git a/frontend/tests/ram.test.js b/frontend/tests/ram.test.js
index 2f8d055..864487a 100644
--- a/frontend/tests/ram.test.js
+++ b/frontend/tests/ram.test.js
@@ -416,16 +416,17 @@ test('ramParseAppStatus keeps 16-byte AIDs (no rawLen-1 truncation)', () => {
assert.strictEqual(apps[3].lifecycle, '07');
});
-test('ramDeleteApdu builds the GP DELETE with the 4F AID TLV and Le (F0414C46416101)', () => {
+test('ramDeleteApdu builds the GP DELETE with the 4F AID TLV (F0414C46416101)', () => {
// GP Card Spec v2.3.1 Table 11-20/23: P1=00, P2.b8 = object / object+related,
- // data = '4F' AID TLV, Le=00. The old handler called an undefined helper
- // and no APDU was ever sent.
+ // data = '4F' AID TLV, case 3 (no Le - a trailing Le byte becomes a
+ // phantom command on the card's SCP80 layer, live 2026-09-28). The old
+ // handler called an undefined helper and no APDU was ever sent.
assert.strictEqual(ramDeleteApdu('F0414C46416101', false),
- '80E40000094F07F0414C4641610100');
+ '80E40000094F07F0414C46416101');
assert.strictEqual(ramDeleteApdu('F0414C46416101', true),
- '80E40080094F07F0414C4641610100');
+ '80E40080094F07F0414C46416101');
assert.strictEqual(ramDeleteApdu('A1130001180002FFF7100E8904000200', true),
- '80E40080124F10A1130001180002FFF7100E890400020000');
+ '80E40080124F10A1130001180002FFF7100E8904000200');
assert.strictEqual(ramDeleteApdu('', false), '');
// the dead helper reference must not come back
assert.ok(!html.includes('_ber_len('), 'undefined _ber_len() call is back');
diff --git a/frontend/tests/ram_counter_flow.test.js b/frontend/tests/ram_counter_flow.test.js
index 2852017..a32d8c6 100644
--- a/frontend/tests/ram_counter_flow.test.js
+++ b/frontend/tests/ram_counter_flow.test.js
@@ -77,7 +77,7 @@ test('accepted delete persists the consumed counter and drops the record', async
decoded: { last_status_word: '9000' } });
await ramDeleteFromExplorer('F0414C46416101', false);
assert.strictEqual(calls.sent.cntr, '0000000005');
- assert.strictEqual(calls.sent.apdu, '80E40000094F07F0414C4641610100');
+ assert.strictEqual(calls.sent.apdu, '80E40000094F07F0414C46416101');
assert.strictEqual(calls.sent.spi2, '01', 'the computed SPI2 byte must be used');
assert.strictEqual(cards[0].cntr, '0000000006',
'the preset must carry the counter the card consumed');
diff --git a/frontend/tests/scripts.test.js b/frontend/tests/scripts.test.js
index 4f649f4..1eafe6b 100644
--- a/frontend/tests/scripts.test.js
+++ b/frontend/tests/scripts.test.js
@@ -84,9 +84,9 @@ test('scriptsParseApdus accepts comments and whitespace, rejects bad lines', ()
test('scp81DeleteApdus builds GP DELETE APDUs per AID', () => {
assert.deepStrictEqual(scp81DeleteApdus(['A000000003000000'], '00'),
- ['80E400000A4F08A00000000300000000']);
+ ['80E400000A4F08A000000003000000']);
assert.deepStrictEqual(scp81DeleteApdus(['A000000003000000', 'A000000100'], '80'),
- ['80E400800A4F08A00000000300000000', '80E40080074F05A00000010000']);
+ ['80E400800A4F08A000000003000000', '80E40080074F05A000000100']);
assert.deepStrictEqual(scp81DeleteApdus([], '00'), []);
});
diff --git a/pyproject.toml b/pyproject.toml
index 47d1f7e..a7a943a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "pysim-simple-server"
-version = "3.6.14"
+version = "3.6.15"
description = "HTTP REST server wrapping pysim for the SIMple PWA"
requires-python = ">=3.8"
# pysim is a git-only dependency installed explicitly by setup.bat/setup.sh.
diff --git a/pysim_simple_server/server.py b/pysim_simple_server/server.py
index 98447c7..e04f467 100644
--- a/pysim_simple_server/server.py
+++ b/pysim_simple_server/server.py
@@ -31,7 +31,7 @@ from osmocom.tlv import BER_TLV_IE
from osmocom.utils import rpad
-VERSION = '3.6.14'
+VERSION = '3.6.15'
MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE