diff --git a/detailed_alerts_cowork.csv b/detailed_alerts_cowork.csv
new file mode 100644
index 000000000..e835bbd85
--- /dev/null
+++ b/detailed_alerts_cowork.csv
@@ -0,0 +1,67 @@
+"File Path","Line Number","Description","Link"
+"src/electron/agent/executor.ts",12180,"Use of a broken or weak cryptographic algorithm","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/66"
+"src/electron/gateway/security.ts",427,"Creating biased random numbers from a cryptographically secure source","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/65"
+"src/electron/agent/tools/batch-image-tools.ts",353,"Shell command built from environment values","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/64"
+"src/electron/memory/MemoryObservationService.ts",87,"Use of password hash with insufficient computational effort","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/63"
+"src/electron/database/SecureSettingsRepository.ts",593,"Use of password hash with insufficient computational effort","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/62"
+"src/electron/agent/tools/registry.ts",8374,"Use of password hash with insufficient computational effort","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/61"
+"src/electron/agent/executor.ts",12180,"Use of password hash with insufficient computational effort","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/60"
+"src/electron/agent/llm/prompt-cache.ts",186,"Use of password hash with insufficient computational effort","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/59"
+"src/electron/agent/llm/prompt-cache.ts",154,"Use of password hash with insufficient computational effort","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/58"
+"src/electron/hooks/server.ts",933,"Information exposure through a stack trace","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/57"
+"src/electron/extensions/pack-registry.ts",93,"Incomplete URL substring sanitization","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/56"
+"src/electron/extensions/pack-registry.ts",92,"Incomplete URL substring sanitization","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/55"
+"src/electron/agent/tools/x-tools.ts",1143,"Incomplete URL substring sanitization","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/54"
+"src/electron/agent/tools/x-tools.ts",1143,"Incomplete URL substring sanitization","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/53"
+"src/electron/agent/tools/x-tools.ts",181,"Incomplete URL substring sanitization","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/52"
+"src/electron/agent/tools/x-tools.ts",181,"Incomplete URL substring sanitization","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/51"
+"src/electron/agent/tools/browser-tools.ts",1575,"Incomplete URL substring sanitization","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/50"
+"src/electron/agent/tools/browser-tools.ts",1535,"Incomplete URL substring sanitization","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/49"
+"src/electron/agent/skill-registry.ts",328,"Incomplete URL substring sanitization","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/48"
+"src/electron/agent/skill-registry.ts",327,"Incomplete URL substring sanitization","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/47"
+"scripts/collect-public-adoption-stats.mjs",68,"Incomplete URL substring sanitization","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/46"
+"src/electron/agent/tools/canvas-tools.ts",316,"Incomplete URL scheme check","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/45"
+"src/renderer/components/BrowserWorkbenchView.tsx",1523,"DOM text reinterpreted as HTML","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/44"
+"src/renderer/components/BrowserView.tsx",156,"DOM text reinterpreted as HTML","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/43"
+"src/renderer/utils/email-html-sanitize.ts",27,"Bad HTML filtering regexp","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/42"
+"src/renderer/components/WebArtifactViewer.tsx",97,"Bad HTML filtering regexp","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/41"
+"src/electron/mailbox/MailboxService.ts",981,"Bad HTML filtering regexp","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/40"
+"src/electron/gateway/channels/email-client.ts",187,"Bad HTML filtering regexp","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/39"
+"src/electron/agent/tools/web-fetch-tools.ts",513,"Bad HTML filtering regexp","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/38"
+"src/electron/agent/tools/gmail-tools.ts",123,"Bad HTML filtering regexp","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/37"
+"resources/skills/llm-wiki/scripts/wiki-workbench-lib.mjs",157,"Bad HTML filtering regexp","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/36"
+"src/renderer/components/MainContent/markdown-normalization.ts",269,"Incomplete string escaping or encoding","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/35"
+"src/electron/utils/pptx-extractor.ts",502,"Incomplete string escaping or encoding","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/34"
+"src/electron/utils/json-utils.ts",175,"Incomplete string escaping or encoding","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/33"
+"src/electron/memory/DurableContextService.ts",73,"Incomplete string escaping or encoding","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/32"
+"src/electron/mailbox/MailboxService.ts",3521,"Incomplete string escaping or encoding","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/31"
+"src/electron/computer-use/window-isolation.ts",99,"Incomplete string escaping or encoding","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/30"
+"src/electron/computer-use/window-isolation.ts",74,"Incomplete string escaping or encoding","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/29"
+"src/electron/browser/browser-workbench-service.ts",106,"Incomplete string escaping or encoding","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/28"
+"src/electron/agent/executor-completion-utils.ts",42,"Incomplete string escaping or encoding","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/27"
+"src/renderer/components/InboxAgentPanel.tsx",261,"Double escaping or unescaping","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/26"
+"src/renderer/components/AssistantMessageContent.tsx",172,"Double escaping or unescaping","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/25"
+"src/electron/utils/pptx-extractor.ts",338,"Double escaping or unescaping","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/24"
+"src/electron/utils/pptx-extractor.ts",168,"Double escaping or unescaping","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/23"
+"src/electron/mailbox/MailboxService.ts",979,"Double escaping or unescaping","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/22"
+"src/electron/gateway/channels/email-client.ts",185,"Double escaping or unescaping","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/21"
+"src/electron/gateway/channels/email.ts",528,"Double escaping or unescaping","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/20"
+"src/electron/agent/tools/gmail-tools.ts",121,"Double escaping or unescaping","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/19"
+"src/electron/agent/search/duckduckgo-provider.ts",158,"Double escaping or unescaping","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/18"
+"resources/skills/llm-wiki/scripts/wiki-workbench-lib.mjs",156,"Double escaping or unescaping","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/17"
+"src/renderer/utils/email-html-sanitize.ts",25,"Incomplete multi-character sanitization","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/16"
+"src/renderer/utils/email-html-sanitize.ts",25,"Incomplete multi-character sanitization","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/15"
+"src/renderer/utils/email-html-sanitize.ts",25,"Incomplete multi-character sanitization","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/14"
+"src/renderer/components/utils/attachment-content.ts",30,"Incomplete multi-character sanitization","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/13"
+"src/electron/utils/pptx-extractor.ts",338,"Incomplete multi-character sanitization","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/12"
+"src/electron/gateway/channels/email.ts",528,"Incomplete multi-character sanitization","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/11"
+"src/electron/agent/tools/web-fetch-tools.ts",582,"Incomplete multi-character sanitization","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/10"
+"src/electron/agent/tools/web-fetch-tools.ts",511,"Incomplete multi-character sanitization","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/9"
+"src/electron/agent/tools/web-fetch-tools.ts",511,"Incomplete multi-character sanitization","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/8"
+"src/electron/agent/tools/web-fetch-tools.ts",511,"Incomplete multi-character sanitization","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/7"
+"src/electron/agent/skills/document.ts",1070,"Incomplete multi-character sanitization","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/6"
+"src/electron/agent/search/duckduckgo-provider.ts",158,"Incomplete multi-character sanitization","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/5"
+"src/electron/utils/webhook-auth.ts",29,"Polynomial regular expression used on uncontrolled data","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/4"
+"src/electron/tunnels/relay.ts",324,"Polynomial regular expression used on uncontrolled data","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/3"
+"src/shared/tool-call-text-sanitizer.ts",64,"Inefficient regular expression","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/2"
+"src/renderer/utils/markdown-autolink.ts",11,"Inefficient regular expression","https://github.com/raid-ppcoe/CoWork-OS/security/code-scanning/1"
diff --git a/resources/skills/llm-wiki/scripts/wiki-workbench-lib.mjs b/resources/skills/llm-wiki/scripts/wiki-workbench-lib.mjs
index d99af51f8..420b9a0ee 100644
--- a/resources/skills/llm-wiki/scripts/wiki-workbench-lib.mjs
+++ b/resources/skills/llm-wiki/scripts/wiki-workbench-lib.mjs
@@ -153,15 +153,22 @@ export function normalizeRelativePath(filePath) {
}
export function stripHtml(html) {
- return String(html || "")
- .replace(/` can't bypass the filter.
+ let text = String(html || "");
+ let prev;
+ do {
+ prev = text;
+ text = text.replace(/<(script|style|svg)\b[^>]*>[\s\S]*?<\/\1\s*>/gi, " ");
+ } while (text !== prev);
+ return text
.replace(/<[^>]+>/g, " ")
.replace(/ /gi, " ")
- .replace(/&/gi, "&")
.replace(/</gi, "<")
.replace(/>/gi, ">")
+ // Unescape ampersands last so a decoded `&` can't form another entity.
+ .replace(/&/gi, "&")
.replace(/\s+/g, " ")
.trim();
}
diff --git a/scripts/collect-public-adoption-stats.mjs b/scripts/collect-public-adoption-stats.mjs
index 162f02a5d..336a50652 100644
--- a/scripts/collect-public-adoption-stats.mjs
+++ b/scripts/collect-public-adoption-stats.mjs
@@ -65,7 +65,13 @@ async function fetchJson(url, options = {}) {
Accept: "application/vnd.github+json",
...options.headers,
};
- if (token && url.includes("api.github.com")) {
+ let host = "";
+ try {
+ host = new URL(url).hostname.toLowerCase();
+ } catch {
+ // non-absolute URL: never attach the token
+ }
+ if (token && host === "api.github.com") {
headers.Authorization = `Bearer ${token}`;
headers["X-GitHub-Api-Version"] = "2022-11-28";
}
diff --git a/src/electron/agent/executor-completion-utils.ts b/src/electron/agent/executor-completion-utils.ts
index 4254c939a..28bfd2dc5 100644
--- a/src/electron/agent/executor-completion-utils.ts
+++ b/src/electron/agent/executor-completion-utils.ts
@@ -39,7 +39,7 @@ export function normalizePromptForContracts(taskPrompt: string): string {
);
const withoutWorkflow = withoutAdditionalContext.replace(
new RegExp(
- `\\n{2}${WORKFLOW_DECOMPOSITION_HEADER.replace(/[()]/g, "\\$&")}\\n[\\s\\S]*?(?=\\n{2}${USER_UPDATE_HEADER}|$)`,
+ `\\n{2}${WORKFLOW_DECOMPOSITION_HEADER.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\n[\\s\\S]*?(?=\\n{2}${USER_UPDATE_HEADER}|$)`,
"g",
),
"",
diff --git a/src/electron/agent/executor.ts b/src/electron/agent/executor.ts
index efdef90b3..a155ba1c2 100644
--- a/src/electron/agent/executor.ts
+++ b/src/electron/agent/executor.ts
@@ -12177,7 +12177,8 @@ ${transcript}
artifactKind: contract.artifactKind,
},
bestCandidateLength: bestCandidate.length,
- bestCandidateHash: createHash("sha1").update(bestCandidate).digest("hex").slice(0, 12),
+ // ponytail: non-security fingerprint for diagnostics, not a password; sha256 to silence weak-algo scan
+ bestCandidateHash: createHash("sha256").update(bestCandidate).digest("hex").slice(0, 12),
directAnswer: {
bestCandidatePasses: this.responseDirectlyAddressesPrompt(bestCandidate, contract),
fallbackPasses: fallbackHasDirectAnswer,
diff --git a/src/electron/agent/search/duckduckgo-provider.ts b/src/electron/agent/search/duckduckgo-provider.ts
index cebdb9b5d..68e3a8915 100644
--- a/src/electron/agent/search/duckduckgo-provider.ts
+++ b/src/electron/agent/search/duckduckgo-provider.ts
@@ -155,17 +155,23 @@ export class DuckDuckGoProvider implements SearchProvider {
}
private stripHtml(html: string): string {
- return html
- .replace(//g, "")
- .replace(/<\/b>/g, "")
- .replace(/<[^>]+>/g, "")
- .replace(/&/g, "&")
+ // Strip tags; loop until stable so a partial tag can't leave a working tag
+ // behind after a single pass.
+ let text = html.replace(//g, "").replace(/<\/b>/g, "");
+ let prev: string;
+ do {
+ prev = text;
+ text = text.replace(/<[^>]+>/g, "");
+ } while (text !== prev);
+ return text
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, '"')
.replace(/'/g, "'")
.replace(/'/g, "'")
.replace(/ /g, " ")
+ // Unescape ampersands last so a decoded `&` can't form another entity.
+ .replace(/&/g, "&")
.replace(/\s+/g, " ");
}
diff --git a/src/electron/agent/skill-registry.ts b/src/electron/agent/skill-registry.ts
index 5c95ddd89..eb3104d96 100644
--- a/src/electron/agent/skill-registry.ts
+++ b/src/electron/agent/skill-registry.ts
@@ -323,9 +323,16 @@ export class SkillRegistry {
*/
private isStaticCatalog(): boolean {
const url = this.registryUrl.toLowerCase();
+ let hostname = "";
+ try {
+ hostname = new URL(this.registryUrl).hostname.toLowerCase();
+ } catch {
+ // not an absolute URL; fall back to path-suffix checks below
+ }
return (
- url.includes("raw.githubusercontent.com") ||
- url.includes("github.io") ||
+ hostname === "raw.githubusercontent.com" ||
+ hostname === "github.io" ||
+ hostname.endsWith(".github.io") ||
url.endsWith("/registry") ||
url.endsWith("/registry/")
);
diff --git a/src/electron/agent/skills/document.ts b/src/electron/agent/skills/document.ts
index a4f210d58..33ed14b16 100644
--- a/src/electron/agent/skills/document.ts
+++ b/src/electron/agent/skills/document.ts
@@ -1066,8 +1066,16 @@ export class DocumentBuilder {
const _trRegex = /]*>([\s\S]*?)<\/tr>/gi;
const _tdThRegex = /]*>([\s\S]*?)<\/t[dh]>/gi;
- // Helper to strip HTML tags
- const stripTags = (str: string): string => str.replace(/<[^>]*>/g, "").trim();
+ // Helper to strip HTML tags. Loop until stable so a partial tag can't leave
+ // a working tag behind after a single pass.
+ const stripTags = (str: string): string => {
+ let prev: string;
+ do {
+ prev = str;
+ str = str.replace(/<[^>]*>/g, "");
+ } while (str !== prev);
+ return str.trim();
+ };
// Process in order of appearance
let _lastIndex = 0;
diff --git a/src/electron/agent/tools/batch-image-tools.ts b/src/electron/agent/tools/batch-image-tools.ts
index 09e1a85be..baf3a09c0 100644
--- a/src/electron/agent/tools/batch-image-tools.ts
+++ b/src/electron/agent/tools/batch-image-tools.ts
@@ -1,4 +1,4 @@
-import { exec, execFile } from "child_process";
+import { execFile } from "child_process";
import { promisify } from "util";
import * as os from "os";
import * as path from "path";
@@ -10,7 +10,6 @@ import { LLMTool } from "../llm/types";
type Any = any; // oxlint-disable-line typescript-eslint(no-explicit-any)
-const execAsync = promisify(exec);
const execFileAsync = promisify(execFile);
const PROCESS_TIMEOUT_MS = 60_000;
const PROTECTED_WRITE_PATHS = [
@@ -349,10 +348,14 @@ export class BatchImageTools {
], { timeout: PROCESS_TIMEOUT_MS });
} catch {
// Fallback: try with `magick composite` (ImageMagick 7)
- await execAsync(
- `magick composite -dissolve ${opacity} -gravity ${gravity} ${JSON.stringify(wmPath)} ${JSON.stringify(inputPath)} ${JSON.stringify(outputPath)}`,
- { timeout: PROCESS_TIMEOUT_MS },
- );
+ await execFileAsync("magick", [
+ "composite",
+ "-dissolve", String(opacity),
+ "-gravity", gravity,
+ wmPath,
+ inputPath,
+ outputPath,
+ ], { timeout: PROCESS_TIMEOUT_MS });
}
}
diff --git a/src/electron/agent/tools/browser-tools.ts b/src/electron/agent/tools/browser-tools.ts
index 49076ad16..e3632b8f4 100644
--- a/src/electron/agent/tools/browser-tools.ts
+++ b/src/electron/agent/tools/browser-tools.ts
@@ -21,6 +21,15 @@ import { normalizeBrowserUrl } from "../../browser/browser-session-manager";
import { evaluateNetworkPolicy } from "../../security/network-policy";
import { BuiltinToolsSettingsManager } from "./builtin-settings";
+/** True if the URL's host is exactly the Google consent page (parsed, not substring-matched). */
+function isGoogleConsentUrl(url: string): boolean {
+ try {
+ return new URL(url).hostname.toLowerCase() === "consent.google.com";
+ } catch {
+ return false;
+ }
+}
+
// oxlint-disable-next-line typescript-eslint/no-explicit-any
type Any = any;
type BrowserProvider = "local" | "browser-use-cloud";
@@ -1532,7 +1541,7 @@ export class BrowserTools {
this.getSessionId(input),
);
const currentUrl = typeof current?.url === "string" ? current.url : "";
- if (!allow_consent && currentUrl.includes("consent.google.com")) {
+ if (!allow_consent && isGoogleConsentUrl(currentUrl)) {
throw new Error("Consent page detected; dismiss consent before taking screenshot.");
}
if (Array.isArray(disallow_url_contains) && disallow_url_contains.length > 0) {
@@ -1572,7 +1581,7 @@ export class BrowserTools {
if (!allow_consent) {
const currentUrl = await this.browserService.getCurrentUrl();
- if (currentUrl.includes("consent.google.com")) {
+ if (isGoogleConsentUrl(currentUrl)) {
throw new Error("Consent page detected; dismiss consent before taking screenshot.");
}
}
diff --git a/src/electron/agent/tools/canvas-tools.ts b/src/electron/agent/tools/canvas-tools.ts
index abf16d707..bbd5be37c 100644
--- a/src/electron/agent/tools/canvas-tools.ts
+++ b/src/electron/agent/tools/canvas-tools.ts
@@ -306,17 +306,11 @@ export class CanvasTools {
const value = String(ref || "").trim().toLowerCase();
if (!value) return true;
if (value.startsWith("#")) return true;
- if (value.startsWith("//")) return true;
- if (
- value.startsWith("http://") ||
- value.startsWith("https://") ||
- value.startsWith("data:") ||
- value.startsWith("blob:") ||
- value.startsWith("about:") ||
- value.startsWith("javascript:")
- ) {
- return true;
- }
+ if (value.startsWith("//")) return true; // protocol-relative URL
+ // Any explicit URI scheme (http:, https:, data:, blob:, about:, javascript:,
+ // vbscript:, filesystem:, etc.) means this is not a workspace-relative path we
+ // should resolve/inline. Detect the scheme robustly instead of enumerating prefixes.
+ if (/^[a-z][a-z0-9+.-]*:/.test(value)) return true;
return false;
}
diff --git a/src/electron/agent/tools/gmail-tools.ts b/src/electron/agent/tools/gmail-tools.ts
index 6650093f3..6af5dcdbd 100644
--- a/src/electron/agent/tools/gmail-tools.ts
+++ b/src/electron/agent/tools/gmail-tools.ts
@@ -118,16 +118,24 @@ function decodeBody(data?: string): string {
}
function stripHtml(html: string): string {
- return html
- .replace(/