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 +117 -14
- package/dist/features/terminal.js +51 -15
- package/dist/index.html +51 -2
- package/dist/utils/probing.js +106 -0
- package/npm-shrinkwrap.json +10 -7
- package/package.json +5 -4
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
|
|
62
|
-
output.textContent += "
|
|
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
|
|
65
|
-
|
|
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
|
-
|
|
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 += "\
|
|
73
|
-
output.textContent +=
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
78
|
-
deps.
|
|
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
|
|
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
|
+
}
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pptb-standard-sample-tool",
|
|
3
|
-
"version": "1.1.12
|
|
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
|
|
9
|
+
"version": "1.1.12",
|
|
10
10
|
"license": "GPL-3.0",
|
|
11
11
|
"devDependencies": {
|
|
12
|
-
"@pptb/types": "^1.0
|
|
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
|
|
60
|
-
"resolved": "https://registry.npmjs.org/@pptb/types/-/types-1.0.
|
|
61
|
-
"integrity": "sha512-
|
|
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.
|
|
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
|
|
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
|
+
}
|