pptb-standard-sample-tool 1.1.2 → 1.1.5
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 +42 -1
- package/dist/app.js +78 -360
- package/dist/features/filesystem.js +291 -0
- package/dist/features/terminal.js +141 -0
- package/dist/icon/icon/sample-icon.png +0 -0
- package/dist/icon/icon/sample-icon.svg +2 -0
- package/dist/index.html +173 -156
- package/dist/security/policy.js +117 -0
- package/dist/security/reporting.js +67 -0
- package/dist/security/suites.js +281 -0
- package/dist/utils/time.js +3 -0
- package/package.json +1 -2
package/dist/index.html
CHANGED
|
@@ -1,169 +1,186 @@
|
|
|
1
|
-
<!
|
|
1
|
+
<!doctype html>
|
|
2
2
|
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>HTML Sample Tool</title>
|
|
7
|
+
<link rel="stylesheet" href="styles.css" />
|
|
8
|
+
</head>
|
|
9
|
+
|
|
10
|
+
<body>
|
|
11
|
+
<div class="container">
|
|
12
|
+
<header>
|
|
13
|
+
<h1>🚀 HTML Sample Tool</h1>
|
|
14
|
+
<p class="subtitle">A complete example of building Power Platform Tool Box tools with HTML & TypeScript</p>
|
|
15
|
+
</header>
|
|
16
|
+
|
|
17
|
+
<!-- Connection Status -->
|
|
18
|
+
<section class="card">
|
|
19
|
+
<h2>🔗 Primary Connection Status</h2>
|
|
20
|
+
<div id="connection-info" class="info-box">
|
|
21
|
+
<div class="loading">Checking connection...</div>
|
|
22
|
+
</div>
|
|
23
|
+
</section>
|
|
3
24
|
|
|
4
|
-
<
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
<link rel="stylesheet" href="styles.css">
|
|
9
|
-
</head>
|
|
10
|
-
|
|
11
|
-
<body>
|
|
12
|
-
<div class="container">
|
|
13
|
-
<header>
|
|
14
|
-
<h1>🚀 HTML Sample Tool</h1>
|
|
15
|
-
<p class="subtitle">A complete example of building Power Platform Tool Box tools with HTML & TypeScript</p>
|
|
16
|
-
</header>
|
|
17
|
-
|
|
18
|
-
<!-- Connection Status -->
|
|
19
|
-
<section class="card">
|
|
20
|
-
<h2>🔗 Primary Connection Status</h2>
|
|
21
|
-
<div id="connection-info" class="info-box">
|
|
22
|
-
<div class="loading">Checking connection...</div>
|
|
23
|
-
</div>
|
|
24
|
-
</section>
|
|
25
|
-
|
|
26
|
-
<section class="card">
|
|
27
|
-
<h2>🔗 Secondary Connection Status</h2>
|
|
28
|
-
<div id="secondary-connection-info" class="info-box">
|
|
29
|
-
<div class="loading">Checking connection...</div>
|
|
30
|
-
</div>
|
|
31
|
-
</section>
|
|
32
|
-
|
|
33
|
-
<!-- ToolBox API Examples -->
|
|
34
|
-
<section class="card">
|
|
35
|
-
<h2>🛠️ ToolBox API Examples</h2>
|
|
36
|
-
|
|
37
|
-
<div class="example-group">
|
|
38
|
-
<h3>Notifications</h3>
|
|
39
|
-
<div class="button-group">
|
|
40
|
-
<button id="show-success-btn" class="btn btn-success">Show Success</button>
|
|
41
|
-
<button id="show-info-btn" class="btn btn-info">Show Info</button>
|
|
42
|
-
<button id="show-warning-btn" class="btn btn-warning">Show Warning</button>
|
|
43
|
-
<button id="show-error-btn" class="btn btn-error">Show Error</button>
|
|
44
|
-
<button id="show-loading-btn" class="btn btn-secondary">Show Loading</button>
|
|
25
|
+
<section class="card">
|
|
26
|
+
<h2>🔗 Secondary Connection Status</h2>
|
|
27
|
+
<div id="secondary-connection-info" class="info-box">
|
|
28
|
+
<div class="loading">Checking connection...</div>
|
|
45
29
|
</div>
|
|
46
|
-
</
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
<
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
<
|
|
30
|
+
</section>
|
|
31
|
+
|
|
32
|
+
<!-- ToolBox API Examples -->
|
|
33
|
+
<section class="card">
|
|
34
|
+
<h2>🛠️ ToolBox API Examples</h2>
|
|
35
|
+
|
|
36
|
+
<div class="example-group">
|
|
37
|
+
<h3>Notifications</h3>
|
|
38
|
+
<div class="button-group">
|
|
39
|
+
<button id="show-success-btn" class="btn btn-success">Show Success</button>
|
|
40
|
+
<button id="show-info-btn" class="btn btn-info">Show Info</button>
|
|
41
|
+
<button id="show-warning-btn" class="btn btn-warning">Show Warning</button>
|
|
42
|
+
<button id="show-error-btn" class="btn btn-error">Show Error</button>
|
|
43
|
+
<button id="show-loading-btn" class="btn btn-secondary">Show Loading</button>
|
|
44
|
+
</div>
|
|
54
45
|
</div>
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
<button id="read-system-file-btn" class="btn btn-error">Read System File (Test)</button>
|
|
64
|
-
<button id="read-hardcoded-file-btn" class="btn btn-error">Read File (Ignore Selection)</button>
|
|
65
|
-
<button id="read-direct-file-btn" class="btn btn-error">Read Hardcoded Path (No Dialog)</button>
|
|
46
|
+
|
|
47
|
+
<div class="example-group">
|
|
48
|
+
<h3>Utilities</h3>
|
|
49
|
+
<div class="button-group">
|
|
50
|
+
<button id="copy-clipboard-btn" class="btn">Copy to Clipboard</button>
|
|
51
|
+
<button id="get-theme-btn" class="btn">Get Theme</button>
|
|
52
|
+
<button id="save-file-btn" class="btn">Save File</button>
|
|
53
|
+
</div>
|
|
66
54
|
</div>
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
55
|
+
|
|
56
|
+
<div class="example-group">
|
|
57
|
+
<h3>File System API</h3>
|
|
58
|
+
<div class="button-group">
|
|
59
|
+
<button id="read-text-btn" class="btn">Read Text File</button>
|
|
60
|
+
<button id="read-directory-btn" class="btn">Read Directory</button>
|
|
61
|
+
<button id="create-directory-btn" class="btn">Create Directory</button>
|
|
62
|
+
<button id="read-system-file-btn" class="btn btn-error">Read System File (Test)</button>
|
|
63
|
+
<button id="read-hardcoded-file-btn" class="btn btn-error">Read File (Ignore Selection)</button>
|
|
64
|
+
<button id="read-direct-file-btn" class="btn btn-error">Read Hardcoded Path (No Dialog)</button>
|
|
65
|
+
</div>
|
|
66
|
+
<div id="filesystem-output" class="output"></div>
|
|
78
67
|
</div>
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
<
|
|
68
|
+
|
|
69
|
+
<div class="example-group">
|
|
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>
|
|
72
|
+
<div class="button-group">
|
|
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
|
+
</div>
|
|
76
|
+
<div id="parallel-output" class="output"></div>
|
|
88
77
|
</div>
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
<p style="margin:4px 0 10px;font-size:12px;opacity:.85">Persist simple configuration values per tool
|
|
100
|
-
instance using the Tool Settings storage API.</p>
|
|
101
|
-
<div class="input-group">
|
|
102
|
-
<label for="settings-fetchxml">Saved FetchXML (used for Account Query demo if present):</label>
|
|
103
|
-
<textarea id="settings-fetchxml"
|
|
104
|
-
style="width:100%;min-height:110px;padding:10px;border:2px solid #e0e0e0;border-radius:6px;font-family:monospace;font-size:12px;line-height:1.4"
|
|
105
|
-
placeholder="Enter FetchXML here and click Save Setting"></textarea>
|
|
78
|
+
|
|
79
|
+
<div class="example-group">
|
|
80
|
+
<h3>Terminal</h3>
|
|
81
|
+
<div class="button-group">
|
|
82
|
+
<button id="create-terminal-btn" class="btn">Create Terminal</button>
|
|
83
|
+
<button id="execute-command-btn" class="btn" disabled>Execute Command</button>
|
|
84
|
+
<button id="run-security-probe-btn" class="btn btn-warning" disabled>Run Security Probe</button>
|
|
85
|
+
<button id="close-terminal-btn" class="btn" disabled>Close Terminal</button>
|
|
86
|
+
</div>
|
|
87
|
+
<div id="terminal-output" class="output"></div>
|
|
106
88
|
</div>
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
<
|
|
89
|
+
|
|
90
|
+
<div class="example-group">
|
|
91
|
+
<h3>Security Test Suites</h3>
|
|
92
|
+
<p style="margin: 4px 0 8px; font-size: 12px; opacity: 0.8">Runs non-destructive API-specific security checks with per-suite JSON reports.</p>
|
|
93
|
+
<div class="button-group">
|
|
94
|
+
<button id="run-terminal-suite-btn" class="btn btn-warning">Run Terminal Suite</button>
|
|
95
|
+
<button id="run-filesystem-suite-btn" class="btn btn-warning">Run FileSystem Suite</button>
|
|
96
|
+
<button id="run-events-suite-btn" class="btn btn-warning">Run Events Suite</button>
|
|
97
|
+
<button id="run-settings-suite-btn" class="btn btn-warning">Run Settings Suite</button>
|
|
98
|
+
<button id="run-dataverse-suite-btn" class="btn btn-warning">Run Dataverse Suite</button>
|
|
99
|
+
<button id="run-security-suite-btn" class="btn btn-error">Run All Suites</button>
|
|
100
|
+
</div>
|
|
101
|
+
<div id="overall-suite-output" class="output"></div>
|
|
102
|
+
<div id="terminal-suite-output" class="output"></div>
|
|
103
|
+
<div id="filesystem-suite-output" class="output"></div>
|
|
104
|
+
<div id="events-suite-output" class="output"></div>
|
|
105
|
+
<div id="settings-suite-output" class="output"></div>
|
|
106
|
+
<div id="dataverse-suite-output" class="output"></div>
|
|
110
107
|
</div>
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
<
|
|
115
|
-
<
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
<
|
|
119
|
-
<
|
|
108
|
+
</section>
|
|
109
|
+
|
|
110
|
+
<!-- Dataverse API Examples -->
|
|
111
|
+
<section class="card">
|
|
112
|
+
<h2>💾 Dataverse API Examples</h2>
|
|
113
|
+
|
|
114
|
+
<div class="example-group">
|
|
115
|
+
<h3>Tool Settings</h3>
|
|
116
|
+
<p style="margin: 4px 0 10px; font-size: 12px; opacity: 0.85">Persist simple configuration values per tool instance using the Tool Settings storage API.</p>
|
|
117
|
+
<div class="input-group">
|
|
118
|
+
<label for="settings-fetchxml">Saved FetchXML (used for Account Query demo if present):</label>
|
|
119
|
+
<textarea
|
|
120
|
+
id="settings-fetchxml"
|
|
121
|
+
style="width: 100%; min-height: 110px; padding: 10px; border: 2px solid #e0e0e0; border-radius: 6px; font-family: monospace; font-size: 12px; line-height: 1.4"
|
|
122
|
+
placeholder="Enter FetchXML here and click Save Setting"
|
|
123
|
+
></textarea>
|
|
124
|
+
</div>
|
|
125
|
+
<div class="button-group">
|
|
126
|
+
<button id="load-setting-btn" class="btn">Load Setting</button>
|
|
127
|
+
<button id="save-setting-btn" class="btn btn-primary">Save Setting</button>
|
|
128
|
+
</div>
|
|
129
|
+
<div id="settings-output" class="output"></div>
|
|
120
130
|
</div>
|
|
121
|
-
|
|
122
|
-
<div
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
<
|
|
131
|
+
|
|
132
|
+
<div class="example-group">
|
|
133
|
+
<h3>Query Records</h3>
|
|
134
|
+
<div class="button-group">
|
|
135
|
+
<button id="query-accounts-btn" class="btn btn-primary">Query Top 10 Accounts (Primary)</button>
|
|
136
|
+
<button id="query-accounts-secondary-btn" class="btn">Query Top 10 Accounts (Secondary)</button>
|
|
137
|
+
<button id="query-contacts-querydata-btn" class="btn">Query Top 10 Contacts (Primary)</button>
|
|
138
|
+
</div>
|
|
139
|
+
<div id="query-output" class="output"></div>
|
|
140
|
+
<div id="query-output-secondary" class="output"></div>
|
|
130
141
|
</div>
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
<
|
|
142
|
+
|
|
143
|
+
<div class="example-group">
|
|
144
|
+
<h3>CRUD Operations</h3>
|
|
145
|
+
<div class="input-group">
|
|
146
|
+
<label for="contact-firstname">Contact First Name:</label>
|
|
147
|
+
<input type="text" id="contact-firstname" placeholder="Enter contact first name" value="Sample" />
|
|
148
|
+
</div>
|
|
149
|
+
<div class="input-group">
|
|
150
|
+
<label for="contact-lastname">Contact Last Name:</label>
|
|
151
|
+
<input type="text" id="contact-lastname" placeholder="Enter contact last name" value="Contact" />
|
|
152
|
+
</div>
|
|
153
|
+
<div class="button-group">
|
|
154
|
+
<button id="create-contact-btn" class="btn btn-primary">Create Contact</button>
|
|
155
|
+
<button id="update-contact-btn" class="btn" disabled>Update Contact</button>
|
|
156
|
+
<button id="delete-contact-btn" class="btn btn-error" disabled>Delete Contact</button>
|
|
157
|
+
</div>
|
|
158
|
+
<div id="crud-output" class="output"></div>
|
|
134
159
|
</div>
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
<
|
|
138
|
-
<button id="
|
|
160
|
+
|
|
161
|
+
<div class="example-group">
|
|
162
|
+
<h3>Metadata</h3>
|
|
163
|
+
<button id="get-metadata-btn" class="btn">Get Contact Metadata</button>
|
|
164
|
+
<button id="get-metadata-attributes" class="btn">Get Account Attributes Metadata</button>
|
|
165
|
+
<button id="get-metadata-allentities" class="btn">Get All Entities Metadata</button>
|
|
166
|
+
<div id="metadata-output" class="output"></div>
|
|
167
|
+
</div>
|
|
168
|
+
|
|
169
|
+
<div class="example-group">
|
|
170
|
+
<h3>Execute Actions</h3>
|
|
171
|
+
<button id="whoami-btn" class="btn">Execute WhoAmI</button>
|
|
172
|
+
<div id="execute-output" class="output"></div>
|
|
139
173
|
</div>
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
<
|
|
144
|
-
<
|
|
145
|
-
<button id="
|
|
146
|
-
<
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
<button id="whoami-btn" class="btn">Execute WhoAmI</button>
|
|
154
|
-
<div id="execute-output" class="output"></div>
|
|
155
|
-
</div>
|
|
156
|
-
</section>
|
|
157
|
-
|
|
158
|
-
<!-- Event Log -->
|
|
159
|
-
<section class="card">
|
|
160
|
-
<h2>📋 Event Log</h2>
|
|
161
|
-
<button id="clear-log-btn" class="btn btn-secondary">Clear Log</button>
|
|
162
|
-
<div id="event-log" class="log"></div>
|
|
163
|
-
</section>
|
|
164
|
-
</div>
|
|
165
|
-
|
|
166
|
-
<script src="app.js" type="module"></script>
|
|
167
|
-
</body>
|
|
168
|
-
|
|
169
|
-
</html>
|
|
174
|
+
</section>
|
|
175
|
+
|
|
176
|
+
<!-- Event Log -->
|
|
177
|
+
<section class="card">
|
|
178
|
+
<h2>📋 Event Log</h2>
|
|
179
|
+
<button id="clear-log-btn" class="btn btn-secondary">Clear Log</button>
|
|
180
|
+
<div id="event-log" class="log"></div>
|
|
181
|
+
</section>
|
|
182
|
+
</div>
|
|
183
|
+
|
|
184
|
+
<script src="app.js" type="module"></script>
|
|
185
|
+
</body>
|
|
186
|
+
</html>
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
const TERMINAL_ALLOWED_COMMANDS = new Set([
|
|
2
|
+
"dir",
|
|
3
|
+
"ls -la",
|
|
4
|
+
"echo PPTB_SECURITY_PROBE",
|
|
5
|
+
"pwd",
|
|
6
|
+
"uname -a",
|
|
7
|
+
"echo CHAIN_TEST && echo SECOND_COMMAND",
|
|
8
|
+
"Get-Location",
|
|
9
|
+
"$PSVersionTable.PSVersion.ToString()",
|
|
10
|
+
'Write-Output "CHAIN_TEST"; Write-Output "SECOND_COMMAND"',
|
|
11
|
+
]);
|
|
12
|
+
const TERMINAL_BLOCKED_TOKENS = [
|
|
13
|
+
"rm ",
|
|
14
|
+
"del ",
|
|
15
|
+
"format",
|
|
16
|
+
"sudo",
|
|
17
|
+
"chmod",
|
|
18
|
+
"chown",
|
|
19
|
+
"powershell -enc",
|
|
20
|
+
"invoke-expression",
|
|
21
|
+
"downloadstring",
|
|
22
|
+
"curl ",
|
|
23
|
+
"wget ",
|
|
24
|
+
"invoke-webrequest",
|
|
25
|
+
"http://",
|
|
26
|
+
"https://",
|
|
27
|
+
"| curl",
|
|
28
|
+
"| wget",
|
|
29
|
+
"base64",
|
|
30
|
+
"scp ",
|
|
31
|
+
"ssh ",
|
|
32
|
+
"id_rsa",
|
|
33
|
+
".ssh",
|
|
34
|
+
"system_profiler",
|
|
35
|
+
"wmic",
|
|
36
|
+
"procdump",
|
|
37
|
+
"memory",
|
|
38
|
+
"dump",
|
|
39
|
+
];
|
|
40
|
+
const FILE_PATH_BLOCK_LIST = ["/etc/passwd", "/etc/shadow", "/etc/hosts", "/var/log/system.log", "/.ssh/", "\\.ssh\\", "id_rsa", "known_hosts", "c:\\windows\\system32"];
|
|
41
|
+
const LOCAL_DATA_TOKENS = ["uname", "system_profiler", "wmic", "ipconfig", "ifconfig", "cat ", "type ", "get-content", "env", "$env:", "whoami", ".ssh", "id_rsa"];
|
|
42
|
+
const NETWORK_EGRESS_TOKENS = ["curl ", "wget ", "invoke-webrequest", "http://", "https://", "ftp://", "scp ", "nc ", "netcat"];
|
|
43
|
+
export function listTerminalAllowedCommands() {
|
|
44
|
+
return Array.from(TERMINAL_ALLOWED_COMMANDS);
|
|
45
|
+
}
|
|
46
|
+
export function listTerminalBlockedTokens() {
|
|
47
|
+
return [...TERMINAL_BLOCKED_TOKENS];
|
|
48
|
+
}
|
|
49
|
+
export function listFilePathBlockedPatterns() {
|
|
50
|
+
return [...FILE_PATH_BLOCK_LIST];
|
|
51
|
+
}
|
|
52
|
+
export function normalizeForPolicy(value) {
|
|
53
|
+
return value.trim().replace(/\\/g, "/").toLowerCase();
|
|
54
|
+
}
|
|
55
|
+
function isAbsolutePath(filePath) {
|
|
56
|
+
return /^([a-zA-Z]:[\\/]|\/)/.test(filePath.trim());
|
|
57
|
+
}
|
|
58
|
+
function hasPathTraversalSegment(normalizedPath) {
|
|
59
|
+
return normalizedPath.includes("/../") || normalizedPath.endsWith("/..") || normalizedPath.startsWith("../");
|
|
60
|
+
}
|
|
61
|
+
export function getBlockedCommandReason(command) {
|
|
62
|
+
const normalized = command.trim();
|
|
63
|
+
if (!normalized) {
|
|
64
|
+
return "Command is empty";
|
|
65
|
+
}
|
|
66
|
+
if (normalized.length > 200) {
|
|
67
|
+
return "Command exceeds maximum allowed length";
|
|
68
|
+
}
|
|
69
|
+
if (/[\r\n]/.test(normalized)) {
|
|
70
|
+
return "Multiline commands are not allowed";
|
|
71
|
+
}
|
|
72
|
+
if (/[\u0000-\u001F\u007F]/.test(normalized)) {
|
|
73
|
+
return "Control characters are not allowed";
|
|
74
|
+
}
|
|
75
|
+
const low = normalizeForPolicy(normalized);
|
|
76
|
+
const hasLocalDataSignal = LOCAL_DATA_TOKENS.some((token) => low.includes(token));
|
|
77
|
+
const hasNetworkSignal = NETWORK_EGRESS_TOKENS.some((token) => low.includes(token));
|
|
78
|
+
if (hasLocalDataSignal && hasNetworkSignal) {
|
|
79
|
+
return "Potential local-data exfiltration pattern detected";
|
|
80
|
+
}
|
|
81
|
+
if (TERMINAL_ALLOWED_COMMANDS.has(normalized)) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
const blockedToken = TERMINAL_BLOCKED_TOKENS.find((token) => low.includes(token));
|
|
85
|
+
if (blockedToken) {
|
|
86
|
+
return `Blocked token detected: ${blockedToken}`;
|
|
87
|
+
}
|
|
88
|
+
return "Command is not on allow-list";
|
|
89
|
+
}
|
|
90
|
+
export function getBlockedPathReason(filePath) {
|
|
91
|
+
if (!isAbsolutePath(filePath)) {
|
|
92
|
+
return "Only absolute paths are allowed";
|
|
93
|
+
}
|
|
94
|
+
const normalized = normalizeForPolicy(filePath);
|
|
95
|
+
if (hasPathTraversalSegment(normalized)) {
|
|
96
|
+
return "Path traversal segments are not allowed";
|
|
97
|
+
}
|
|
98
|
+
const blockedMatch = FILE_PATH_BLOCK_LIST.find((token) => normalized.includes(token));
|
|
99
|
+
if (blockedMatch) {
|
|
100
|
+
return `Sensitive path pattern detected: ${blockedMatch}`;
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
export async function executeCommandWithPolicyGuard(toolbox, terminalId, command) {
|
|
105
|
+
const blockedReason = getBlockedCommandReason(command);
|
|
106
|
+
if (blockedReason) {
|
|
107
|
+
throw new Error(`Terminal command blocked by policy. ${blockedReason}`);
|
|
108
|
+
}
|
|
109
|
+
await toolbox.terminal.execute(terminalId, command);
|
|
110
|
+
}
|
|
111
|
+
export async function readTextWithPolicyGuard(toolbox, filePath) {
|
|
112
|
+
const blockedReason = getBlockedPathReason(filePath);
|
|
113
|
+
if (blockedReason) {
|
|
114
|
+
throw new Error(`File path blocked by policy. ${blockedReason}`);
|
|
115
|
+
}
|
|
116
|
+
return toolbox.fileSystem.readText(filePath);
|
|
117
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { getBlockedCommandReason, normalizeForPolicy } from "./policy.js";
|
|
2
|
+
export function addSecurityResult(results, name, status, severity, details) {
|
|
3
|
+
results.push({ name, status, severity, details });
|
|
4
|
+
}
|
|
5
|
+
export function assertCommandBlockedWithToken(name, command, expectedToken, severity = "critical") {
|
|
6
|
+
const reason = getBlockedCommandReason(command);
|
|
7
|
+
const pass = reason !== null && normalizeForPolicy(reason).includes(normalizeForPolicy(expectedToken));
|
|
8
|
+
return {
|
|
9
|
+
name,
|
|
10
|
+
status: pass ? "pass" : "fail",
|
|
11
|
+
severity,
|
|
12
|
+
details: pass ? `Command blocked with expected token: ${expectedToken}` : `Expected token '${expectedToken}' not found in block reason. Reason: ${reason || "none"}`,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export function getHighestSeverity(results) {
|
|
16
|
+
const order = ["low", "medium", "high", "critical"];
|
|
17
|
+
let highest = "low";
|
|
18
|
+
for (const r of results) {
|
|
19
|
+
if (order.indexOf(r.severity) > order.indexOf(highest)) {
|
|
20
|
+
highest = r.severity;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return highest;
|
|
24
|
+
}
|
|
25
|
+
function sortResultsForTriage(results) {
|
|
26
|
+
const severityOrder = {
|
|
27
|
+
critical: 0,
|
|
28
|
+
high: 1,
|
|
29
|
+
medium: 2,
|
|
30
|
+
low: 3,
|
|
31
|
+
};
|
|
32
|
+
const statusOrder = {
|
|
33
|
+
fail: 0,
|
|
34
|
+
pass: 1,
|
|
35
|
+
};
|
|
36
|
+
return [...results].sort((a, b) => {
|
|
37
|
+
const bySeverity = severityOrder[a.severity] - severityOrder[b.severity];
|
|
38
|
+
if (bySeverity !== 0)
|
|
39
|
+
return bySeverity;
|
|
40
|
+
const byStatus = statusOrder[a.status] - statusOrder[b.status];
|
|
41
|
+
if (byStatus !== 0)
|
|
42
|
+
return byStatus;
|
|
43
|
+
return a.name.localeCompare(b.name);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
export function renderSuiteReport(outputId, suiteName, results) {
|
|
47
|
+
const output = document.getElementById(outputId);
|
|
48
|
+
const sortedResults = sortResultsForTriage(results);
|
|
49
|
+
const failed = results.filter((r) => r.status === "fail").length;
|
|
50
|
+
const passed = results.length - failed;
|
|
51
|
+
const highestSeverity = getHighestSeverity(results);
|
|
52
|
+
const report = {
|
|
53
|
+
generatedAt: new Date().toISOString(),
|
|
54
|
+
suite: suiteName,
|
|
55
|
+
summary: {
|
|
56
|
+
total: results.length,
|
|
57
|
+
passed,
|
|
58
|
+
failed,
|
|
59
|
+
highestSeverity,
|
|
60
|
+
},
|
|
61
|
+
results: sortedResults,
|
|
62
|
+
};
|
|
63
|
+
if (output) {
|
|
64
|
+
output.textContent = JSON.stringify(report, null, 2);
|
|
65
|
+
}
|
|
66
|
+
return { failed, passed, total: results.length, highestSeverity };
|
|
67
|
+
}
|