pptb-standard-sample-tool 1.1.2 → 1.1.3
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/README.md +36 -0
- package/dist/app.js +249 -5
- package/dist/icon/icon/sample-icon.png +0 -0
- package/dist/icon/icon/sample-icon.svg +2 -0
- package/dist/index.html +10 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -94,6 +94,8 @@ html-sample/
|
|
|
94
94
|
**Terminal:**
|
|
95
95
|
- Create isolated terminal instances
|
|
96
96
|
- Execute shell commands
|
|
97
|
+
- Run a safe terminal security probe (non-destructive)
|
|
98
|
+
- Run a Security Test Suite with pass/fail JSON report
|
|
97
99
|
- View command output
|
|
98
100
|
- Close terminal when done
|
|
99
101
|
|
|
@@ -150,6 +152,40 @@ const dataverse: typeof window.dataverseAPI = window.dataverseAPI;
|
|
|
150
152
|
3. **Rebuild:** Run `npm run build`
|
|
151
153
|
4. **Reload Tool:** In Power Platform Tool Box, close and reopen the tool
|
|
152
154
|
|
|
155
|
+
## Security Testing Guidance
|
|
156
|
+
|
|
157
|
+
Use the built-in **Run Security Probe** button in the Terminal section to validate terminal exposure with safe commands only.
|
|
158
|
+
|
|
159
|
+
What it checks:
|
|
160
|
+
- Terminal can be created and receives command output
|
|
161
|
+
- Basic command execution works
|
|
162
|
+
- Command chaining is possible (risk signal if unrestricted)
|
|
163
|
+
|
|
164
|
+
What it does not do:
|
|
165
|
+
- No destructive commands
|
|
166
|
+
- No credential, SSH, or private file access attempts
|
|
167
|
+
- No process memory dumping attempts
|
|
168
|
+
|
|
169
|
+
If the probe succeeds, treat that as a signal to enforce stricter host-side controls in Power Platform Tool Box:
|
|
170
|
+
- Command allow-listing
|
|
171
|
+
- Path allow-listing for filesystem APIs
|
|
172
|
+
- Auditing/logging for terminal command execution
|
|
173
|
+
|
|
174
|
+
This sample now includes policy guards in [src/app.ts](src/app.ts):
|
|
175
|
+
- `TERMINAL_ALLOWED_COMMANDS`: explicit commands allowed to run
|
|
176
|
+
- `TERMINAL_BLOCKED_TOKENS`: deny-list for risky command fragments
|
|
177
|
+
- `FILE_PATH_BLOCK_LIST`: blocks sensitive filesystem locations (for example `.ssh` and `/etc/*` paths)
|
|
178
|
+
- `executeCommandWithPolicyGuard(...)` and `readTextWithPolicyGuard(...)`: central enforcement wrappers
|
|
179
|
+
|
|
180
|
+
It also includes a **Security Test Suite** button that runs non-destructive checks and emits a JSON report:
|
|
181
|
+
- command allow-list enforcement
|
|
182
|
+
- risky token detection
|
|
183
|
+
- path normalization and sensitive path matching
|
|
184
|
+
- malformed terminal event payload resilience
|
|
185
|
+
- lightweight burst/rate handling probe
|
|
186
|
+
|
|
187
|
+
For production PPTB host hardening, apply equivalent checks in the host process (server-side / main-process boundary), not only in tool UI code.
|
|
188
|
+
|
|
153
189
|
## API Usage Examples
|
|
154
190
|
|
|
155
191
|
### Advanced Utilities
|
package/dist/app.js
CHANGED
|
@@ -18,6 +18,190 @@ let currentConnection = null;
|
|
|
18
18
|
let secondaryConnection = null;
|
|
19
19
|
let currentTerminal = null;
|
|
20
20
|
let createdId = null;
|
|
21
|
+
const TERMINAL_ALLOWED_COMMANDS = new Set([
|
|
22
|
+
"dir",
|
|
23
|
+
"ls -la",
|
|
24
|
+
"echo PPTB_SECURITY_PROBE",
|
|
25
|
+
"pwd",
|
|
26
|
+
"uname -a",
|
|
27
|
+
"echo CHAIN_TEST && echo SECOND_COMMAND",
|
|
28
|
+
"Get-Location",
|
|
29
|
+
"$PSVersionTable.PSVersion.ToString()",
|
|
30
|
+
'Write-Output "CHAIN_TEST"; Write-Output "SECOND_COMMAND"',
|
|
31
|
+
]);
|
|
32
|
+
const TERMINAL_BLOCKED_TOKENS = [
|
|
33
|
+
"rm ",
|
|
34
|
+
"del ",
|
|
35
|
+
"format",
|
|
36
|
+
"sudo",
|
|
37
|
+
"chmod",
|
|
38
|
+
"chown",
|
|
39
|
+
"powershell -enc",
|
|
40
|
+
"invoke-expression",
|
|
41
|
+
"downloadstring",
|
|
42
|
+
"curl ",
|
|
43
|
+
"wget ",
|
|
44
|
+
"scp ",
|
|
45
|
+
"ssh ",
|
|
46
|
+
"id_rsa",
|
|
47
|
+
".ssh",
|
|
48
|
+
"procdump",
|
|
49
|
+
"memory",
|
|
50
|
+
"dump",
|
|
51
|
+
];
|
|
52
|
+
const FILE_PATH_BLOCK_LIST = [
|
|
53
|
+
"/etc/passwd",
|
|
54
|
+
"/etc/shadow",
|
|
55
|
+
"/etc/hosts",
|
|
56
|
+
"/var/log/system.log",
|
|
57
|
+
"/.ssh/",
|
|
58
|
+
"\\.ssh\\",
|
|
59
|
+
"id_rsa",
|
|
60
|
+
"known_hosts",
|
|
61
|
+
"c:\\windows\\system32",
|
|
62
|
+
];
|
|
63
|
+
function delay(ms) {
|
|
64
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
65
|
+
}
|
|
66
|
+
function normalizeForPolicy(value) {
|
|
67
|
+
return value.trim().replace(/\\/g, "/").toLowerCase();
|
|
68
|
+
}
|
|
69
|
+
function getBlockedCommandReason(command) {
|
|
70
|
+
const normalized = command.trim();
|
|
71
|
+
if (TERMINAL_ALLOWED_COMMANDS.has(normalized)) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
const low = normalizeForPolicy(normalized);
|
|
75
|
+
const blockedToken = TERMINAL_BLOCKED_TOKENS.find((token) => low.includes(token));
|
|
76
|
+
if (blockedToken) {
|
|
77
|
+
return `Blocked token detected: ${blockedToken}`;
|
|
78
|
+
}
|
|
79
|
+
return "Command is not on allow-list";
|
|
80
|
+
}
|
|
81
|
+
function getBlockedPathReason(filePath) {
|
|
82
|
+
const normalized = normalizeForPolicy(filePath);
|
|
83
|
+
const blockedMatch = FILE_PATH_BLOCK_LIST.find((token) => normalized.includes(token));
|
|
84
|
+
if (blockedMatch) {
|
|
85
|
+
return `Sensitive path pattern detected: ${blockedMatch}`;
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
async function executeCommandWithPolicyGuard(terminalId, command) {
|
|
90
|
+
const blockedReason = getBlockedCommandReason(command);
|
|
91
|
+
if (blockedReason) {
|
|
92
|
+
throw new Error(`Terminal command blocked by policy. ${blockedReason}`);
|
|
93
|
+
}
|
|
94
|
+
await toolbox.terminal.execute(terminalId, command);
|
|
95
|
+
}
|
|
96
|
+
async function readTextWithPolicyGuard(filePath) {
|
|
97
|
+
const blockedReason = getBlockedPathReason(filePath);
|
|
98
|
+
if (blockedReason) {
|
|
99
|
+
throw new Error(`File path blocked by policy. ${blockedReason}`);
|
|
100
|
+
}
|
|
101
|
+
return toolbox.fileSystem.readText(filePath);
|
|
102
|
+
}
|
|
103
|
+
function addSecurityResult(results, name, status, details) {
|
|
104
|
+
results.push({ name, status, details });
|
|
105
|
+
}
|
|
106
|
+
async function runSecurityTestSuite() {
|
|
107
|
+
const output = document.getElementById("security-suite-output");
|
|
108
|
+
if (output) {
|
|
109
|
+
output.textContent = "Running security test suite...\n";
|
|
110
|
+
}
|
|
111
|
+
const results = [];
|
|
112
|
+
try {
|
|
113
|
+
// 1) Command allow-list exactness
|
|
114
|
+
const blockedByAllowList = getBlockedCommandReason("ls") !== null;
|
|
115
|
+
addSecurityResult(results, "command_allowlist_exact_match", blockedByAllowList ? "pass" : "fail", blockedByAllowList
|
|
116
|
+
? "Non-allow-listed command was blocked"
|
|
117
|
+
: "Command not in allow-list was unexpectedly allowed");
|
|
118
|
+
// 2) Risky token detection
|
|
119
|
+
const blockedDangerousToken = getBlockedCommandReason("echo ok && curl http://example.com") !== null;
|
|
120
|
+
addSecurityResult(results, "command_blocked_token_detection", blockedDangerousToken ? "pass" : "fail", blockedDangerousToken
|
|
121
|
+
? "Risky token pattern was blocked"
|
|
122
|
+
: "Risky token pattern bypassed command policy");
|
|
123
|
+
// 3) Path normalization / traversal style checks
|
|
124
|
+
const traversalPath = "../../Users/test/.ssh/id_rsa";
|
|
125
|
+
const traversalBlocked = getBlockedPathReason(traversalPath) !== null;
|
|
126
|
+
addSecurityResult(results, "path_normalization_sensitive_match", traversalBlocked ? "pass" : "fail", traversalBlocked
|
|
127
|
+
? "Sensitive path pattern detected after normalization"
|
|
128
|
+
: "Sensitive path pattern was not detected");
|
|
129
|
+
// 4) Windows-style sensitive path checks
|
|
130
|
+
const windowsSensitivePath = "C:\\Users\\demo\\.ssh\\known_hosts";
|
|
131
|
+
const windowsPathBlocked = getBlockedPathReason(windowsSensitivePath) !== null;
|
|
132
|
+
addSecurityResult(results, "windows_path_sensitive_match", windowsPathBlocked ? "pass" : "fail", windowsPathBlocked
|
|
133
|
+
? "Windows-style sensitive path pattern detected"
|
|
134
|
+
: "Windows-style sensitive path pattern was not detected");
|
|
135
|
+
// 5) Malformed terminal output payload handling
|
|
136
|
+
try {
|
|
137
|
+
handleTerminalOutput(undefined);
|
|
138
|
+
addSecurityResult(results, "terminal_output_malformed_payload", "pass", "Malformed terminal output payload did not crash handler");
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
addSecurityResult(results, "terminal_output_malformed_payload", "fail", `Handler crashed: ${error.message}`);
|
|
142
|
+
}
|
|
143
|
+
// 6) Malformed command-completed payload handling
|
|
144
|
+
try {
|
|
145
|
+
handleCommandCompleted(undefined);
|
|
146
|
+
addSecurityResult(results, "terminal_completed_malformed_payload", "pass", "Malformed command-completed payload did not crash handler");
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
addSecurityResult(results, "terminal_completed_malformed_payload", "fail", `Handler crashed: ${error.message}`);
|
|
150
|
+
}
|
|
151
|
+
// 7) Lightweight terminal burst/rate handling
|
|
152
|
+
try {
|
|
153
|
+
if (!currentTerminal) {
|
|
154
|
+
await createTerminal();
|
|
155
|
+
}
|
|
156
|
+
if (!currentTerminal) {
|
|
157
|
+
addSecurityResult(results, "terminal_burst_probe", "fail", "Could not create terminal for burst probe");
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
const burst = Array.from({ length: 5 }).map((_, i) => `echo PPTB_SECURITY_PROBE_BURST_${i + 1}`);
|
|
161
|
+
for (const command of burst) {
|
|
162
|
+
try {
|
|
163
|
+
await executeCommandWithPolicyGuard(currentTerminal.id, command);
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
// intentional: this checks if policy blocks unknown commands quickly and safely
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
addSecurityResult(results, "terminal_burst_probe", "pass", "Burst of rapid commands processed without app crash");
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
addSecurityResult(results, "terminal_burst_probe", "fail", `Burst probe error: ${error.message}`);
|
|
174
|
+
}
|
|
175
|
+
const failed = results.filter((r) => r.status === "fail").length;
|
|
176
|
+
const passed = results.length - failed;
|
|
177
|
+
const report = {
|
|
178
|
+
generatedAt: new Date().toISOString(),
|
|
179
|
+
summary: {
|
|
180
|
+
total: results.length,
|
|
181
|
+
passed,
|
|
182
|
+
failed,
|
|
183
|
+
},
|
|
184
|
+
results,
|
|
185
|
+
};
|
|
186
|
+
if (output) {
|
|
187
|
+
output.textContent = JSON.stringify(report, null, 2);
|
|
188
|
+
}
|
|
189
|
+
if (failed > 0) {
|
|
190
|
+
await showNotification("Security Suite Completed", `${failed} test(s) failed. Review JSON report.`, "warning");
|
|
191
|
+
log(`Security suite completed with ${failed} failure(s)`, "warning");
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
await showNotification("Security Suite Completed", "All security tests passed", "success");
|
|
195
|
+
log("Security suite completed with all tests passing", "success");
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
if (output) {
|
|
200
|
+
output.textContent = `Security suite error: ${error.message}`;
|
|
201
|
+
}
|
|
202
|
+
log(`Security suite execution error: ${error.message}`, "error");
|
|
203
|
+
}
|
|
204
|
+
}
|
|
21
205
|
/**
|
|
22
206
|
* Initialize the application
|
|
23
207
|
*/
|
|
@@ -182,6 +366,8 @@ function setupEventHandlers() {
|
|
|
182
366
|
// Terminal buttons
|
|
183
367
|
document.getElementById("create-terminal-btn")?.addEventListener("click", createTerminal);
|
|
184
368
|
document.getElementById("execute-command-btn")?.addEventListener("click", executeTerminalCommand);
|
|
369
|
+
document.getElementById("run-security-probe-btn")?.addEventListener("click", runTerminalSecurityProbe);
|
|
370
|
+
document.getElementById("run-security-suite-btn")?.addEventListener("click", runSecurityTestSuite);
|
|
185
371
|
document.getElementById("close-terminal-btn")?.addEventListener("click", closeTerminal);
|
|
186
372
|
// Dataverse query button
|
|
187
373
|
document.getElementById("query-accounts-btn")?.addEventListener("click", queryAccounts);
|
|
@@ -317,7 +503,7 @@ async function readText() {
|
|
|
317
503
|
}
|
|
318
504
|
if (output)
|
|
319
505
|
output.textContent = `Reading file: ${filePath}\n\n`;
|
|
320
|
-
const content = await
|
|
506
|
+
const content = await readTextWithPolicyGuard(filePath);
|
|
321
507
|
if (output) {
|
|
322
508
|
output.textContent += `File Size: ${content.length} characters\n\n`;
|
|
323
509
|
output.textContent += "Content:\n";
|
|
@@ -440,7 +626,7 @@ async function readSystemFile() {
|
|
|
440
626
|
if (output)
|
|
441
627
|
output.textContent = `Attempting to read system file: ${systemFilePath}\n\n`;
|
|
442
628
|
log(`Attempting to read system file: ${systemFilePath}`, "info");
|
|
443
|
-
const content = await
|
|
629
|
+
const content = await readTextWithPolicyGuard(systemFilePath);
|
|
444
630
|
if (output) {
|
|
445
631
|
output.textContent += `File Size: ${content.length} characters\n\n`;
|
|
446
632
|
output.textContent += "Content (first 1000 chars):\n";
|
|
@@ -503,7 +689,7 @@ async function readHardcodedFile() {
|
|
|
503
689
|
if (output)
|
|
504
690
|
output.textContent += `Attempting to read hardcoded path: ${hardcodedPath}\n\n`;
|
|
505
691
|
log(`Attempting to read hardcoded path: ${hardcodedPath}`, "info");
|
|
506
|
-
const content = await
|
|
692
|
+
const content = await readTextWithPolicyGuard(hardcodedPath);
|
|
507
693
|
if (output) {
|
|
508
694
|
output.textContent += `✓ Success! File Size: ${content.length} characters\n\n`;
|
|
509
695
|
output.textContent += "Content:\n";
|
|
@@ -545,7 +731,7 @@ async function readDirectFile() {
|
|
|
545
731
|
if (output)
|
|
546
732
|
output.textContent += `Path: ${hardcodedPath}\n\n`;
|
|
547
733
|
log(`Attempting direct read of hardcoded path: ${hardcodedPath}`, "info");
|
|
548
|
-
const content = await
|
|
734
|
+
const content = await readTextWithPolicyGuard(hardcodedPath);
|
|
549
735
|
if (output) {
|
|
550
736
|
output.textContent += `✓ Success! File Size: ${content.length} characters\n\n`;
|
|
551
737
|
output.textContent += "Content:\n";
|
|
@@ -583,9 +769,12 @@ async function createTerminal() {
|
|
|
583
769
|
log(`Terminal created: ${currentTerminal.name} (${currentTerminal.id})`, "success");
|
|
584
770
|
// Enable command buttons
|
|
585
771
|
const executeBtn = document.getElementById("execute-command-btn");
|
|
772
|
+
const probeBtn = document.getElementById("run-security-probe-btn");
|
|
586
773
|
const closeBtn = document.getElementById("close-terminal-btn");
|
|
587
774
|
if (executeBtn)
|
|
588
775
|
executeBtn.disabled = false;
|
|
776
|
+
if (probeBtn)
|
|
777
|
+
probeBtn.disabled = false;
|
|
589
778
|
if (closeBtn)
|
|
590
779
|
closeBtn.disabled = false;
|
|
591
780
|
await showNotification("Terminal Created", `Terminal ${currentTerminal.name} is ready`, "success");
|
|
@@ -610,12 +799,60 @@ async function executeTerminalCommand() {
|
|
|
610
799
|
output.textContent = `> ${command}\n`;
|
|
611
800
|
}
|
|
612
801
|
log(`Executing command: ${command}`, "info");
|
|
613
|
-
await
|
|
802
|
+
await executeCommandWithPolicyGuard(currentTerminal.id, command);
|
|
614
803
|
}
|
|
615
804
|
catch (error) {
|
|
616
805
|
log(`Error executing command: ${error.message}`, "error");
|
|
617
806
|
}
|
|
618
807
|
}
|
|
808
|
+
/**
|
|
809
|
+
* Run a safe terminal security probe.
|
|
810
|
+
* This validates terminal exposure and command chaining with non-malicious commands only.
|
|
811
|
+
*/
|
|
812
|
+
async function runTerminalSecurityProbe() {
|
|
813
|
+
try {
|
|
814
|
+
if (!currentTerminal) {
|
|
815
|
+
await createTerminal();
|
|
816
|
+
}
|
|
817
|
+
if (!currentTerminal) {
|
|
818
|
+
await showNotification("Terminal Unavailable", "Unable to create terminal for probe", "error");
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
const output = document.getElementById("terminal-output");
|
|
822
|
+
const isWindows = navigator.platform.toLowerCase().includes("win");
|
|
823
|
+
const probeCommands = isWindows
|
|
824
|
+
? [
|
|
825
|
+
"echo PPTB_SECURITY_PROBE",
|
|
826
|
+
"Get-Location",
|
|
827
|
+
"$PSVersionTable.PSVersion.ToString()",
|
|
828
|
+
"Write-Output \"CHAIN_TEST\"; Write-Output \"SECOND_COMMAND\"",
|
|
829
|
+
]
|
|
830
|
+
: ["echo PPTB_SECURITY_PROBE", "pwd", "uname -a", "echo CHAIN_TEST && echo SECOND_COMMAND"];
|
|
831
|
+
if (output) {
|
|
832
|
+
output.textContent = "Running safe terminal security probe...\n";
|
|
833
|
+
output.textContent += "This probe does not execute destructive commands.\n\n";
|
|
834
|
+
}
|
|
835
|
+
log("Running safe terminal security probe", "warning");
|
|
836
|
+
for (const command of probeCommands) {
|
|
837
|
+
if (output) {
|
|
838
|
+
output.textContent += `> ${command}\n`;
|
|
839
|
+
}
|
|
840
|
+
await executeCommandWithPolicyGuard(currentTerminal.id, command);
|
|
841
|
+
await delay(300);
|
|
842
|
+
}
|
|
843
|
+
if (output) {
|
|
844
|
+
output.textContent += "\nProbe summary:\n";
|
|
845
|
+
output.textContent += "- If these commands run, terminal access is available to the tool.\n";
|
|
846
|
+
output.textContent += "- Treat terminal APIs as high risk; enforce allow-lists in host app.\n";
|
|
847
|
+
output.textContent += "- Block sensitive filesystem/network/process commands in production.\n";
|
|
848
|
+
}
|
|
849
|
+
await showNotification("Security Probe Complete", "Review terminal output for risk indicators", "warning");
|
|
850
|
+
log("Security probe completed", "warning");
|
|
851
|
+
}
|
|
852
|
+
catch (error) {
|
|
853
|
+
log(`Error running security probe: ${error.message}`, "error");
|
|
854
|
+
}
|
|
855
|
+
}
|
|
619
856
|
/**
|
|
620
857
|
* Close terminal
|
|
621
858
|
*/
|
|
@@ -628,9 +865,12 @@ async function closeTerminal() {
|
|
|
628
865
|
currentTerminal = null;
|
|
629
866
|
// Disable command buttons
|
|
630
867
|
const executeBtn = document.getElementById("execute-command-btn");
|
|
868
|
+
const probeBtn = document.getElementById("run-security-probe-btn");
|
|
631
869
|
const closeBtn = document.getElementById("close-terminal-btn");
|
|
632
870
|
if (executeBtn)
|
|
633
871
|
executeBtn.disabled = true;
|
|
872
|
+
if (probeBtn)
|
|
873
|
+
probeBtn.disabled = true;
|
|
634
874
|
if (closeBtn)
|
|
635
875
|
closeBtn.disabled = true;
|
|
636
876
|
const output = document.getElementById("terminal-output");
|
|
@@ -645,6 +885,8 @@ async function closeTerminal() {
|
|
|
645
885
|
* Handle terminal output events
|
|
646
886
|
*/
|
|
647
887
|
function handleTerminalOutput(data) {
|
|
888
|
+
if (!data || typeof data !== "object")
|
|
889
|
+
return;
|
|
648
890
|
if (!currentTerminal || data.terminalId !== currentTerminal.id)
|
|
649
891
|
return;
|
|
650
892
|
const output = document.getElementById("terminal-output");
|
|
@@ -657,6 +899,8 @@ function handleTerminalOutput(data) {
|
|
|
657
899
|
* Handle command completed events
|
|
658
900
|
*/
|
|
659
901
|
function handleCommandCompleted(data) {
|
|
902
|
+
if (!data || typeof data !== "object")
|
|
903
|
+
return;
|
|
660
904
|
if (!currentTerminal || data.terminalId !== currentTerminal.id)
|
|
661
905
|
return;
|
|
662
906
|
const output = document.getElementById("terminal-output");
|
|
Binary file
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
|
2
|
+
<svg fill="#000000" width="800px" height="800px" viewBox="0 -2 18 18" xmlns="http://www.w3.org/2000/svg"><path d="M5.24264069,6.65685425 L0.292893219,1.70710678 C-0.0976310729,1.31658249 -0.0976310729,0.683417511 0.292893219,0.292893219 C0.683417511,-0.0976310729 1.31658249,-0.0976310729 1.70710678,0.292893219 L7.36396103,5.94974747 C7.75448532,6.34027176 7.75448532,6.97343674 7.36396103,7.36396103 L1.70710678,13.0208153 C1.31658249,13.4113396 0.683417511,13.4113396 0.292893219,13.0208153 C-0.0976310729,12.630291 -0.0976310729,11.997126 0.292893219,11.6066017 L5.24264069,6.65685425 Z M9,11 L17,11 C17.5522847,11 18,11.4477153 18,12 C18,12.5522847 17.5522847,13 17,13 L9,13 C8.44771525,13 8,12.5522847 8,12 C8,11.4477153 8.44771525,11 9,11 Z"/></svg>
|
package/dist/index.html
CHANGED
|
@@ -84,10 +84,20 @@
|
|
|
84
84
|
<div class="button-group">
|
|
85
85
|
<button id="create-terminal-btn" class="btn">Create Terminal</button>
|
|
86
86
|
<button id="execute-command-btn" class="btn" disabled>Execute Command</button>
|
|
87
|
+
<button id="run-security-probe-btn" class="btn btn-warning" disabled>Run Security Probe</button>
|
|
87
88
|
<button id="close-terminal-btn" class="btn" disabled>Close Terminal</button>
|
|
88
89
|
</div>
|
|
89
90
|
<div id="terminal-output" class="output"></div>
|
|
90
91
|
</div>
|
|
92
|
+
|
|
93
|
+
<div class="example-group">
|
|
94
|
+
<h3>Security Test Suite</h3>
|
|
95
|
+
<p style="margin:4px 0 8px; font-size:12px; opacity:.8;">Runs non-destructive checks for policy bypass attempts and API robustness.</p>
|
|
96
|
+
<div class="button-group">
|
|
97
|
+
<button id="run-security-suite-btn" class="btn btn-warning">Run Security Test Suite</button>
|
|
98
|
+
</div>
|
|
99
|
+
<div id="security-suite-output" class="output"></div>
|
|
100
|
+
</div>
|
|
91
101
|
</section>
|
|
92
102
|
|
|
93
103
|
<!-- Dataverse API Examples -->
|
package/package.json
CHANGED