pptb-standard-sample-tool 1.1.12-beta.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/app.js CHANGED
@@ -15,6 +15,7 @@ import { createSecuritySuites } from "./security/suites.js";
15
15
  // Global API references
16
16
  const toolbox = window.toolboxAPI;
17
17
  const dataverse = window.dataverseAPI;
18
+ const powerplatform = window.powerplatformAPI;
18
19
  // Application state
19
20
  let currentConnection = null;
20
21
  let secondaryConnection = null;
@@ -189,7 +190,6 @@ function setupEventHandlers() {
189
190
  document.getElementById("show-info-btn")?.addEventListener("click", () => showNotification("Information", "This is an informational message", "info"));
190
191
  document.getElementById("show-warning-btn")?.addEventListener("click", () => showNotification("Warning", "Please review this warning", "warning"));
191
192
  document.getElementById("show-error-btn")?.addEventListener("click", () => showNotification("Error", "An error has occurred", "error"));
192
- document.getElementById("show-loading-btn")?.addEventListener("click", showLoading);
193
193
  // Utility buttons
194
194
  document.getElementById("copy-clipboard-btn")?.addEventListener("click", copyToClipboard);
195
195
  document.getElementById("get-theme-btn")?.addEventListener("click", showCurrentTheme);
@@ -226,6 +226,11 @@ function setupEventHandlers() {
226
226
  document.getElementById("get-metadata-allentities")?.addEventListener("click", getAllEntities);
227
227
  //Execute buttons
228
228
  document.getElementById("whoami-btn")?.addEventListener("click", executeWhoAmI);
229
+ // Power Platform API buttons
230
+ document.getElementById("list-apps-btn")?.addEventListener("click", listPowerApps);
231
+ document.getElementById("list-flows-btn")?.addEventListener("click", listPowerAutomateFlows);
232
+ document.getElementById("list-environments-btn")?.addEventListener("click", listEnvironments);
233
+ document.getElementById("get-governance-btn")?.addEventListener("click", getGovernanceData);
229
234
  // Clear log button
230
235
  document.getElementById("clear-log-btn")?.addEventListener("click", clearLog);
231
236
  // Advanced utilities demos
@@ -252,17 +257,6 @@ async function showNotification(title, body, type) {
252
257
  log(`Error showing notification: ${error.message}`, "error");
253
258
  }
254
259
  }
255
- async function showLoading() {
256
- try {
257
- await toolbox.utils.showLoading("Loading... for 3 seconds");
258
- log("Loading shown for 3 seconds", "info");
259
- await new Promise((resolve) => setTimeout(resolve, 3000));
260
- await toolbox.utils.hideLoading();
261
- }
262
- catch (error) {
263
- log(`Error showing loading: ${error.message}`, "error");
264
- }
265
- }
266
260
  /**
267
261
  * Copy text to clipboard
268
262
  */
@@ -885,7 +879,6 @@ async function demoLoading() {
885
879
  if (output)
886
880
  output.textContent = "Showing loading screen...\n";
887
881
  try {
888
- await toolbox.utils.showLoading("Processing data...");
889
882
  log("Loading screen displayed", "info");
890
883
  // Simulate async work or perform a lightweight query
891
884
  if (currentConnection) {
@@ -910,13 +903,123 @@ async function demoLoading() {
910
903
  await showNotification("Loading Demo Error", error.message, "error");
911
904
  }
912
905
  finally {
913
- await toolbox.utils.hideLoading();
914
906
  if (output)
915
907
  output.textContent += "\nLoading screen hidden.";
916
908
  log("Loading screen hidden", "info");
917
909
  await showNotification("Loading Complete", "Demo finished", "success");
918
910
  }
919
911
  }
912
+ // -----------------------------
913
+ // Power Platform API Examples
914
+ // -----------------------------
915
+ async function listPowerApps() {
916
+ const output = document.getElementById("powerapps-output");
917
+ const envId = document.getElementById("ppapps-env-id")?.value;
918
+ if (!envId) {
919
+ await showNotification("Missing Environment ID", "Please enter an environment ID", "warning");
920
+ if (output)
921
+ output.textContent = "Enter an environment ID to list apps.";
922
+ return;
923
+ }
924
+ try {
925
+ if (output)
926
+ output.textContent = "Fetching Power Apps...\n";
927
+ const result = await powerplatform.PowerApps.Get(`environments/${envId}/apps?api-version=2024-10-01`);
928
+ if (output) {
929
+ const apps = result.value || [];
930
+ output.textContent += `Found ${apps.length} app(s):\n\n`;
931
+ apps.forEach((app, index) => {
932
+ output.textContent += `${index + 1}. ${app.name}\n`;
933
+ output.textContent += ` ID: ${app.id || app.appId}\n`;
934
+ output.textContent += ` Type: ${app.type || "N/A"}\n\n`;
935
+ });
936
+ log(`Listed ${apps.length} Power Apps`, "success");
937
+ }
938
+ }
939
+ catch (error) {
940
+ if (output)
941
+ output.textContent = `Error: ${error.message}`;
942
+ log(`Error listing Power Apps: ${error.message}`, "error");
943
+ }
944
+ }
945
+ async function listPowerAutomateFlows() {
946
+ const output = document.getElementById("powerautomate-output");
947
+ const envId = document.getElementById("ppautomate-env-id")?.value;
948
+ if (!envId) {
949
+ await showNotification("Missing Environment ID", "Please enter an environment ID", "warning");
950
+ if (output)
951
+ output.textContent = "Enter an environment ID to list flows.";
952
+ return;
953
+ }
954
+ try {
955
+ if (output)
956
+ output.textContent = "Fetching Power Automate flows...\n";
957
+ const result = await powerplatform.PowerAutomate.Get(`environments/${envId}/cloudFlows?api-version=2024-10-01`);
958
+ if (output) {
959
+ const flows = result.value || [];
960
+ output.textContent += `Found ${flows.length} flow(s):\n\n`;
961
+ flows.forEach((flow, index) => {
962
+ output.textContent += `${index + 1}. ${flow.displayName || flow.name}\n`;
963
+ output.textContent += ` ID: ${flow.id || flow.flowId}\n`;
964
+ output.textContent += ` State: ${flow.state || "N/A"}\n\n`;
965
+ });
966
+ log(`Listed ${flows.length} Power Automate flows`, "success");
967
+ }
968
+ }
969
+ catch (error) {
970
+ if (output)
971
+ output.textContent = `Error: ${error.message}`;
972
+ log(`Error listing flows: ${error.message}`, "error");
973
+ }
974
+ }
975
+ async function listEnvironments() {
976
+ const output = document.getElementById("environments-output");
977
+ try {
978
+ if (output)
979
+ output.textContent = "Fetching environments...\n";
980
+ const result = await powerplatform.EnvironmentManagement.Get("environments?api-version=2024-10-01");
981
+ if (output) {
982
+ const envs = result.value || [];
983
+ output.textContent += `Found ${envs.length} environment(s):\n\n`;
984
+ envs.forEach((env, index) => {
985
+ output.textContent += `${index + 1}. ${env.displayName}\n`;
986
+ output.textContent += ` ID: ${env.id || env.environmentId}\n`;
987
+ output.textContent += ` Type: ${env.type || env.environmentType || "N/A"}\n\n`;
988
+ });
989
+ log(`Listed ${envs.length} environments`, "success");
990
+ }
991
+ }
992
+ catch (error) {
993
+ if (output)
994
+ output.textContent = `Error: ${error.message}`;
995
+ log(`Error listing environments: ${error.message}`, "error");
996
+ }
997
+ }
998
+ async function getGovernanceData() {
999
+ const output = document.getElementById("governance-output");
1000
+ const envId = document.getElementById("governance-env-id")?.value;
1001
+ if (!envId) {
1002
+ await showNotification("Missing Environment ID", "Please enter an environment ID", "warning");
1003
+ if (output)
1004
+ output.textContent = "Enter an environment ID to get governance data.";
1005
+ return;
1006
+ }
1007
+ try {
1008
+ if (output)
1009
+ output.textContent = "Fetching governance data...\n";
1010
+ const result = await powerplatform.Governance.Get(`ruleBasedPolicies/environments/${envId}/assignments?includeRuleSetCounts=true&api-version=2024-10-01`);
1011
+ if (output) {
1012
+ output.textContent += "Governance Data:\n\n";
1013
+ output.textContent += JSON.stringify(result, null, 2);
1014
+ }
1015
+ log("Governance data retrieved", "success");
1016
+ }
1017
+ catch (error) {
1018
+ if (output)
1019
+ output.textContent = `Error: ${error.message}`;
1020
+ log(`Error getting governance data: ${error.message}`, "error");
1021
+ }
1022
+ }
920
1023
  // Initialize when DOM is ready
921
1024
  if (document.readyState === "loading") {
922
1025
  document.addEventListener("DOMContentLoaded", initialize);
@@ -1,4 +1,5 @@
1
1
  import { executeCommandWithPolicyGuard } from "../security/policy.js";
2
+ import { buildProbeCommands, PROBE_TIMEOUT_MS, withTimeout } from "../utils/probing.js";
2
3
  import { delay } from "../utils/time.js";
3
4
  export function createTerminalFeature(deps) {
4
5
  async function createTerminal() {
@@ -54,28 +55,63 @@ export function createTerminalFeature(deps) {
54
55
  }
55
56
  const output = document.getElementById("terminal-output");
56
57
  const isWindows = navigator.platform.toLowerCase().includes("win");
57
- const probeCommands = isWindows
58
- ? ["echo PPTB_SECURITY_PROBE", "Get-Location", "$PSVersionTable.PSVersion.ToString()", 'Write-Output "CHAIN_TEST"; Write-Output "SECOND_COMMAND"']
59
- : ["echo PPTB_SECURITY_PROBE", "pwd", "uname -a", "echo CHAIN_TEST && echo SECOND_COMMAND"];
58
+ const probeCommands = buildProbeCommands(isWindows);
60
59
  if (output) {
61
- output.textContent = "Running safe terminal security probe...\n";
62
- output.textContent += "This probe does not execute destructive commands.\n\n";
60
+ output.textContent = "Running terminal security probe (allow/block list validation)...\n";
61
+ output.textContent += "All probes are non-destructive and self-cleaning.\n\n";
63
62
  }
64
- deps.log("Running safe terminal security probe", "warning");
65
- for (const command of probeCommands) {
63
+ deps.log("Running terminal security probe", "warning");
64
+ const results = [];
65
+ for (const probe of probeCommands) {
66
66
  if (output)
67
- output.textContent += `> ${command}\n`;
68
- await executeCommandWithPolicyGuard(deps.toolbox, terminal.id, command);
67
+ output.textContent += `> [${probe.category}] ${probe.command}\n`;
68
+ let actualBlocked;
69
+ let errorMessage;
70
+ try {
71
+ // ASSUMPTION: executeCommandWithPolicyGuard rejects/throws when the policy
72
+ // blocks a command. If your guard instead resolves with a { blocked: true }
73
+ // shape, replace the detection below accordingly.
74
+ await withTimeout(executeCommandWithPolicyGuard(deps.toolbox, terminal.id, probe.command), PROBE_TIMEOUT_MS);
75
+ actualBlocked = false;
76
+ }
77
+ catch (error) {
78
+ const message = error.message;
79
+ if (message === "PROBE_TIMEOUT") {
80
+ actualBlocked = "timeout";
81
+ errorMessage = "Command did not complete within timeout — may have opened an interactive prompt";
82
+ }
83
+ else {
84
+ actualBlocked = true;
85
+ errorMessage = message;
86
+ }
87
+ }
88
+ const pass = actualBlocked === probe.expectBlocked;
89
+ results.push({ ...probe, actualBlocked, pass, errorMessage });
90
+ if (output) {
91
+ const status = actualBlocked === "timeout" ? "TIMEOUT" : actualBlocked ? "BLOCKED" : "ALLOWED";
92
+ output.textContent += ` -> ${status} (expected ${probe.expectBlocked ? "BLOCKED" : "ALLOWED"}) ${pass ? "✓ PASS" : "✗ FAIL"}\n`;
93
+ }
69
94
  await delay(300);
70
95
  }
96
+ const failures = results.filter((r) => !r.pass);
97
+ const timeouts = results.filter((r) => r.actualBlocked === "timeout");
71
98
  if (output) {
72
- output.textContent += "\nProbe summary:\n";
73
- output.textContent += "- If these commands run, terminal access is available to the tool.\n";
74
- output.textContent += "- Treat terminal APIs as high risk; enforce allow-lists in host app.\n";
75
- output.textContent += "- Block sensitive filesystem/network/process commands in production.\n";
99
+ output.textContent += "\n--- Probe Summary ---\n";
100
+ output.textContent += `Total: ${results.length} Passed: ${results.length - failures.length} Failed: ${failures.length} Timeouts: ${timeouts.length}\n\n`;
101
+ if (failures.length > 0) {
102
+ output.textContent += "Failures (mismatch between expected and actual):\n";
103
+ for (const f of failures) {
104
+ output.textContent += ` [${f.category}] "${f.command}" — expected ${f.expectBlocked ? "BLOCKED" : "ALLOWED"}, got ${f.actualBlocked}\n`;
105
+ }
106
+ output.textContent += "\nAny FAIL where a dangerous command was expected BLOCKED but came back ALLOWED is a real security gap — fix the blocklist before shipping.\n";
107
+ }
108
+ else {
109
+ output.textContent += "All probes matched expectations.\n";
110
+ }
76
111
  }
77
- await deps.showNotification("Security Probe Complete", "Review terminal output for risk indicators", "warning");
78
- deps.log("Security probe completed", "warning");
112
+ const severity = failures.some((f) => f.expectBlocked && f.actualBlocked === false) ? "error" : "warning";
113
+ await deps.showNotification("Security Probe Complete", failures.length > 0 ? `${failures.length} mismatch(es) found — review terminal output` : "All checks passed", severity);
114
+ deps.log(`Security probe completed: ${results.length - failures.length}/${results.length} passed`, severity);
79
115
  }
80
116
  catch (error) {
81
117
  deps.log(`Error running security probe: ${error.message}`, "error");
package/dist/index.html CHANGED
@@ -68,10 +68,9 @@
68
68
 
69
69
  <div class="example-group">
70
70
  <h3>Advanced Utilities</h3>
71
- <p style="margin: 4px 0 8px; font-size: 12px; opacity: 0.8">Demonstrates <code>executeParallel</code>, <code>showLoading</code> & <code>hideLoading</code>.</p>
71
+ <p style="margin: 4px 0 8px; font-size: 12px; opacity: 0.8">Demonstrates <code>executeParallel</code>.</p>
72
72
  <div class="button-group">
73
73
  <button id="parallel-demo-btn" class="btn btn-primary">Run Parallel Demo</button>
74
- <button id="loading-demo-btn" class="btn btn-secondary">Run Loading Demo</button>
75
74
  </div>
76
75
  <div id="parallel-output" class="output"></div>
77
76
  </div>
@@ -173,6 +172,56 @@
173
172
  </div>
174
173
  </section>
175
174
 
175
+ <!-- Power Platform API Examples -->
176
+ <section class="card">
177
+ <h2>⚡ Power Platform API Examples</h2>
178
+ <p style="margin: 4px 0 15px; font-size: 12px; opacity: 0.85">Generic HTTP methods for Power Platform Admin APIs (Power Apps, Power Automate, Environment Management, Governance).</p>
179
+
180
+ <div class="example-group">
181
+ <h3>Power Apps API</h3>
182
+ <div class="input-group">
183
+ <label for="ppapps-env-id">Environment ID:</label>
184
+ <input type="text" id="ppapps-env-id" placeholder="Enter environment ID" />
185
+ </div>
186
+ <div class="button-group">
187
+ <button id="list-apps-btn" class="btn">List Admin Apps</button>
188
+ </div>
189
+ <div id="powerapps-output" class="output"></div>
190
+ </div>
191
+
192
+ <div class="example-group">
193
+ <h3>Power Automate API</h3>
194
+ <div class="input-group">
195
+ <label for="ppautomate-env-id">Environment ID:</label>
196
+ <input type="text" id="ppautomate-env-id" placeholder="Enter environment ID" />
197
+ </div>
198
+ <div class="button-group">
199
+ <button id="list-flows-btn" class="btn">List Flows</button>
200
+ </div>
201
+ <div id="powerautomate-output" class="output"></div>
202
+ </div>
203
+
204
+ <div class="example-group">
205
+ <h3>Environment Management API</h3>
206
+ <div class="button-group">
207
+ <button id="list-environments-btn" class="btn btn-primary">List Environments For User</button>
208
+ </div>
209
+ <div id="environments-output" class="output"></div>
210
+ </div>
211
+
212
+ <div class="example-group">
213
+ <h3>Governance API</h3>
214
+ <div class="input-group">
215
+ <label for="governance-env-id">Environment ID:</label>
216
+ <input type="text" id="governance-env-id" placeholder="Enter environment ID" />
217
+ </div>
218
+ <div class="button-group">
219
+ <button id="get-governance-btn" class="btn">List Rule Assignments By Environment Id</button>
220
+ </div>
221
+ <div id="governance-output" class="output"></div>
222
+ </div>
223
+ </section>
224
+
176
225
  <!-- Event Log -->
177
226
  <section class="card">
178
227
  <h2>📋 Event Log</h2>
@@ -0,0 +1,106 @@
1
+ export const PROBE_TIMEOUT_MS = 5000;
2
+ export function buildProbeCommands(isWindows) {
3
+ return isWindows
4
+ ? [
5
+ // --- Baseline: must remain ALLOWED ---
6
+ { command: "echo PPTB_SECURITY_PROBE", expectBlocked: false, category: "baseline" },
7
+ { command: "Get-Location", expectBlocked: false, category: "baseline" },
8
+ { command: "$PSVersionTable.PSVersion.ToString()", expectBlocked: false, category: "baseline" },
9
+ { command: 'Write-Output "CHAIN_TEST"; Write-Output "SECOND_COMMAND"', expectBlocked: false, category: "baseline-chaining" },
10
+ // --- Direct shell invocation ---
11
+ { command: "cmd.exe /c echo PROBE_LEAK", expectBlocked: true, category: "shell-direct" },
12
+ { command: 'powershell.exe -Command "echo PROBE_LEAK"', expectBlocked: true, category: "shell-direct" },
13
+ { command: "CMD.EXE /c echo PROBE_LEAK", expectBlocked: true, category: "shell-direct-case", note: "Tests case-insensitive matching" },
14
+ // --- Privilege escalation ---
15
+ { command: "runas /user:Administrator cmd", expectBlocked: true, category: "privesc" },
16
+ // --- Path bypass ---
17
+ { command: "C:\\Windows\\System32\\cmd.exe /c echo PROBE_LEAK", expectBlocked: true, category: "path-bypass", note: "Tests realpath resolution vs literal name match" },
18
+ // --- LOLBins (harmless flags only, no real payload) ---
19
+ { command: "mshta.exe about:blank", expectBlocked: true, category: "lolbin" },
20
+ { command: "certutil.exe -?", expectBlocked: true, category: "lolbin" },
21
+ { command: "regsvr32.exe /?", expectBlocked: true, category: "lolbin" },
22
+ { command: "wmic.exe os get caption", expectBlocked: true, category: "lolbin" },
23
+ // --- WSL escape ---
24
+ { command: "wsl echo PROBE_LEAK", expectBlocked: true, category: "wsl-escape" },
25
+ // --- Interpreter eval-flag bypass ---
26
+ { command: "node -e \"console.log('PROBE_LEAK')\"", expectBlocked: true, category: "flag-bypass" },
27
+ { command: "python -c \"print('PROBE_LEAK')\"", expectBlocked: true, category: "flag-bypass" },
28
+ { command: "php -r \"echo 'PROBE_LEAK';\"", expectBlocked: true, category: "flag-bypass" },
29
+ // --- Package-manager dlx/exec equivalents ---
30
+ { command: "npx --call echo", expectBlocked: true, category: "subcommand-bypass" },
31
+ { command: "pnpm dlx cowsay PROBE_LEAK", expectBlocked: true, category: "subcommand-bypass" },
32
+ // --- Shell-builtin / language-feature leak (the architecture test) ---
33
+ {
34
+ command: "Invoke-Expression 'Write-Output PROBE_LEAK'",
35
+ expectBlocked: true,
36
+ category: "shell-builtin-leak",
37
+ note: "If this is NOT blocked, arbitrary execution is possible via PS language features regardless of your binary blocklist",
38
+ },
39
+ {
40
+ command: "Start-Process cmd.exe -ArgumentList '/c echo PROBE_LEAK' -NoNewWindow -Wait",
41
+ expectBlocked: true,
42
+ category: "shell-builtin-leak",
43
+ note: "Spawns a real but transient cmd.exe if not caught",
44
+ },
45
+ ]
46
+ : [
47
+ // --- Baseline: must remain ALLOWED ---
48
+ { command: "echo PPTB_SECURITY_PROBE", expectBlocked: false, category: "baseline" },
49
+ { command: "pwd", expectBlocked: false, category: "baseline" },
50
+ { command: "uname -a", expectBlocked: false, category: "baseline" },
51
+ { command: "echo CHAIN_TEST && echo SECOND_COMMAND", expectBlocked: false, category: "baseline-chaining" },
52
+ // --- Direct shell invocation ---
53
+ { command: "bash -c 'echo PROBE_LEAK'", expectBlocked: true, category: "shell-direct" },
54
+ { command: "zsh -c 'echo PROBE_LEAK'", expectBlocked: true, category: "shell-direct" },
55
+ { command: "/bin/bash -c 'echo PROBE_LEAK'", expectBlocked: true, category: "shell-direct-path", note: "Tests realpath resolution vs literal basename match" },
56
+ // --- Privilege escalation (non-interactive flags to avoid a hanging password prompt) ---
57
+ { command: "sudo -n echo PROBE_LEAK", expectBlocked: true, category: "privesc" },
58
+ { command: "pkexec echo PROBE_LEAK", expectBlocked: true, category: "privesc" },
59
+ // --- Interpreter eval-flag bypass ---
60
+ { command: "python3 -c \"print('PROBE_LEAK')\"", expectBlocked: true, category: "flag-bypass" },
61
+ { command: "perl -e \"print 'PROBE_LEAK'\"", expectBlocked: true, category: "flag-bypass" },
62
+ { command: "ruby -e \"puts 'PROBE_LEAK'\"", expectBlocked: true, category: "flag-bypass" },
63
+ { command: "node -e \"console.log('PROBE_LEAK')\"", expectBlocked: true, category: "flag-bypass" },
64
+ // --- Pager/editor shell escapes, forced non-interactive so the probe can't stall ---
65
+ { command: "echo PROBE_LEAK | less -F", expectBlocked: true, category: "shell-escape-tool", note: "-F makes less exit immediately instead of waiting on a keypress" },
66
+ { command: "man ls | cat", expectBlocked: true, category: "shell-escape-tool" },
67
+ { command: "vim -es -c ':!echo PROBE_LEAK' -c ':q'", expectBlocked: true, category: "shell-escape-tool", note: "-es = silent ex mode, can't drop into an interactive screen" },
68
+ // --- find/awk side-channel execution ---
69
+ { command: "find . -maxdepth 0 -exec echo PROBE_LEAK \\;", expectBlocked: true, category: "side-channel-exec" },
70
+ { command: "awk 'BEGIN{print \"PROBE_LEAK\"}'", expectBlocked: true, category: "side-channel-exec" },
71
+ // --- Remote access (non-networking variants only — no real listeners/connections) ---
72
+ { command: "nc -h", expectBlocked: true, category: "remote-access", note: "Help flag only, never opens a listener" },
73
+ { command: "ssh -o ProxyCommand='echo PROBE_LEAK' -o ConnectTimeout=1 127.0.0.1 exit", expectBlocked: true, category: "remote-access" },
74
+ // --- macOS only — remove if your CI/dev box is Linux ---
75
+ { command: "osascript -e 'return \"PROBE_LEAK\"'", expectBlocked: true, category: "macos-specific" },
76
+ // --- Package-manager dlx/exec equivalents ---
77
+ { command: "npx -c 'echo PROBE_LEAK'", expectBlocked: true, category: "subcommand-bypass" },
78
+ { command: "npm exec -- echo PROBE_LEAK", expectBlocked: true, category: "subcommand-bypass" },
79
+ { command: "pnpm dlx cowsay PROBE_LEAK", expectBlocked: true, category: "subcommand-bypass" },
80
+ // --- Wrapper/indirection bypass (the hard cases) ---
81
+ { command: "env bash -c 'echo PROBE_LEAK'", expectBlocked: true, category: "wrapper-bypass", note: "argv[0] is 'env', not 'bash' — tests full-argument inspection, not just argv[0]" },
82
+ {
83
+ command: "cp /bin/bash /tmp/pptb_probe_bash && /tmp/pptb_probe_bash -c 'echo PROBE_LEAK'; rm -f /tmp/pptb_probe_bash",
84
+ expectBlocked: true,
85
+ category: "wrapper-bypass",
86
+ note: "Tests binary-identity detection vs basename-only matching; self-cleans the copied file",
87
+ },
88
+ // --- Env var injection ---
89
+ { command: "LD_PRELOAD=/nonexistent.so echo PROBE_LEAK", expectBlocked: true, category: "env-injection" },
90
+ { command: "GIT_SSH_COMMAND='echo PROBE_LEAK' git --version", expectBlocked: true, category: "env-injection" },
91
+ ];
92
+ }
93
+ export function withTimeout(promise, ms) {
94
+ return new Promise((resolve, reject) => {
95
+ const timer = setTimeout(() => reject(new Error("PROBE_TIMEOUT")), ms);
96
+ promise
97
+ .then((value) => {
98
+ clearTimeout(timer);
99
+ resolve(value);
100
+ })
101
+ .catch((err) => {
102
+ clearTimeout(timer);
103
+ reject(err);
104
+ });
105
+ });
106
+ }
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "pptb-standard-sample-tool",
3
- "version": "1.1.12-beta.1",
3
+ "version": "1.1.12",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pptb-standard-sample-tool",
9
- "version": "1.1.12-beta.1",
9
+ "version": "1.1.12",
10
10
  "license": "GPL-3.0",
11
11
  "devDependencies": {
12
- "@pptb/types": "^1.0.19",
12
+ "@pptb/types": "^1.2.4-beta.0",
13
13
  "shx": "^0.4.0",
14
14
  "typescript": "^5.0.0"
15
15
  },
@@ -56,11 +56,14 @@
56
56
  }
57
57
  },
58
58
  "node_modules/@pptb/types": {
59
- "version": "1.0.19",
60
- "resolved": "https://registry.npmjs.org/@pptb/types/-/types-1.0.19.tgz",
61
- "integrity": "sha512-1nzI65TZEcP5t672G8T3uzheDI5az7Q58HGOq7F2wh0oxcbLKjSfarD13cNKR01slyBIUTNUF5W3pNHJTLYHYA==",
59
+ "version": "1.2.4-beta.0",
60
+ "resolved": "https://registry.npmjs.org/@pptb/types/-/types-1.2.4-beta.0.tgz",
61
+ "integrity": "sha512-KEY2RjPkxaMkv3XOF48pzGlTiGT2bWdseUgsr2YA9itGkjMj8zegSUiqU6Rb2SA6omdPy1HzD2v5iEB3G4R7ZQ==",
62
62
  "dev": true,
63
- "license": "GPL-3.0"
63
+ "license": "GPL-3.0",
64
+ "bin": {
65
+ "pptb-validate": "bin/pptb-validate.js"
66
+ }
64
67
  },
65
68
  "node_modules/braces": {
66
69
  "version": "3.0.3",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pptb-standard-sample-tool",
3
- "version": "1.1.12-beta.1",
3
+ "version": "1.2.0",
4
4
  "displayName": "HTML Sample Tool",
5
5
  "description": "A sample Power Platform ToolBox tool built with HTML, CSS, and TypeScript",
6
6
  "main": "index.html",
@@ -28,7 +28,8 @@
28
28
  },
29
29
  "features": {
30
30
  "minAPI": "1.2.2",
31
- "multiConnection": "optional"
31
+ "multiConnection": "optional",
32
+ "enabledForPowerPlatformAPI": true
32
33
  },
33
34
  "repository": {
34
35
  "type": "git",
@@ -47,7 +48,7 @@
47
48
  "watch": "tsc --watch"
48
49
  },
49
50
  "devDependencies": {
50
- "@pptb/types": "^1.0.19",
51
+ "@pptb/types": "^1.2.4-beta.0",
51
52
  "shx": "^0.4.0",
52
53
  "typescript": "^5.0.0"
53
54
  },
@@ -55,4 +56,4 @@
55
56
  "dist",
56
57
  "npm-shrinkwrap.json"
57
58
  ]
58
- }
59
+ }