pptb-standard-sample-tool 1.1.12-beta.0 → 1.1.12
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/features/terminal.js +51 -15
- package/dist/utils/probing.js +106 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
|
@@ -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");
|
|
@@ -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,12 +1,12 @@
|
|
|
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
12
|
"@pptb/types": "^1.0.19",
|
package/package.json
CHANGED