mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-20 11:19:56 +08:00
feat: centralize content wrapping in handleCommandInternal response path
Single wrapping location replaces fragmented per-handler wrapping: - Scoped tokens: content filters + datamarking + enhanced envelope - Root tokens: existing basic wrapping (backward compat) - Chain subcommands exempt from top-level wrapping (wrapped individually) - Adds 'attrs' to PAGE_CONTENT_COMMANDS (ARIA value exposure defense) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -44,7 +44,7 @@ export const ALL_COMMANDS = new Set([...READ_COMMANDS, ...WRITE_COMMANDS, ...MET
|
|||||||
|
|
||||||
/** Commands that return untrusted third-party page content */
|
/** Commands that return untrusted third-party page content */
|
||||||
export const PAGE_CONTENT_COMMANDS = new Set([
|
export const PAGE_CONTENT_COMMANDS = new Set([
|
||||||
'text', 'html', 'links', 'forms', 'accessibility',
|
'text', 'html', 'links', 'forms', 'accessibility', 'attrs',
|
||||||
'console', 'dialog',
|
'console', 'dialog',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ import { handleMetaCommand } from './meta-commands';
|
|||||||
import { handleCookiePickerRoute } from './cookie-picker-routes';
|
import { handleCookiePickerRoute } from './cookie-picker-routes';
|
||||||
import { sanitizeExtensionUrl } from './sidebar-utils';
|
import { sanitizeExtensionUrl } from './sidebar-utils';
|
||||||
import { COMMAND_DESCRIPTIONS, PAGE_CONTENT_COMMANDS, wrapUntrustedContent } from './commands';
|
import { COMMAND_DESCRIPTIONS, PAGE_CONTENT_COMMANDS, wrapUntrustedContent } from './commands';
|
||||||
|
import {
|
||||||
|
wrapUntrustedPageContent, datamarkContent,
|
||||||
|
runContentFilters, type ContentFilterResult,
|
||||||
|
} from './content-security';
|
||||||
import { handleSnapshot, SNAPSHOT_FLAGS } from './snapshot';
|
import { handleSnapshot, SNAPSHOT_FLAGS } from './snapshot';
|
||||||
import {
|
import {
|
||||||
initRegistry, validateToken as validateScopedToken, checkScope, checkDomain,
|
initRegistry, validateToken as validateScopedToken, checkScope, checkDomain,
|
||||||
@@ -954,11 +958,6 @@ async function handleCommandInternal(
|
|||||||
|
|
||||||
if (READ_COMMANDS.has(command)) {
|
if (READ_COMMANDS.has(command)) {
|
||||||
result = await handleReadCommand(command, args, browserManager);
|
result = await handleReadCommand(command, args, browserManager);
|
||||||
// Content wrapping for page-content commands (scoped vs root handled here)
|
|
||||||
// Chain subcommands: each gets wrapped individually here. Chain result is NOT re-wrapped.
|
|
||||||
if (PAGE_CONTENT_COMMANDS.has(command)) {
|
|
||||||
result = wrapUntrustedContent(result, browserManager.getCurrentUrl());
|
|
||||||
}
|
|
||||||
} else if (WRITE_COMMANDS.has(command)) {
|
} else if (WRITE_COMMANDS.has(command)) {
|
||||||
result = await handleWriteCommand(command, args, browserManager);
|
result = await handleWriteCommand(command, args, browserManager);
|
||||||
} else if (META_COMMANDS.has(command)) {
|
} else if (META_COMMANDS.has(command)) {
|
||||||
@@ -1002,6 +1001,35 @@ async function handleCommandInternal(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── Centralized content wrapping (single location for all commands) ───
|
||||||
|
// Scoped tokens: content filter + enhanced envelope + datamarking
|
||||||
|
// Root tokens: basic untrusted content wrapper (backward compat)
|
||||||
|
// Chain exempt from top-level wrapping (each subcommand wrapped individually)
|
||||||
|
if (PAGE_CONTENT_COMMANDS.has(command) && command !== 'chain') {
|
||||||
|
const isScoped = tokenInfo && tokenInfo.clientId !== 'root';
|
||||||
|
if (isScoped) {
|
||||||
|
// Run content filters
|
||||||
|
const filterResult: ContentFilterResult = runContentFilters(
|
||||||
|
result, browserManager.getCurrentUrl(), command,
|
||||||
|
);
|
||||||
|
if (filterResult.blocked) {
|
||||||
|
return { status: 403, json: true, result: JSON.stringify({ error: filterResult.message }) };
|
||||||
|
}
|
||||||
|
// Datamark text command output only (not html, forms, or structured data)
|
||||||
|
if (command === 'text') {
|
||||||
|
result = datamarkContent(result);
|
||||||
|
}
|
||||||
|
// Enhanced envelope wrapping for scoped tokens
|
||||||
|
result = wrapUntrustedPageContent(
|
||||||
|
result, command,
|
||||||
|
filterResult.warnings.length > 0 ? filterResult.warnings : undefined,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Root token: basic wrapping (backward compat, Decision 2)
|
||||||
|
result = wrapUntrustedContent(result, browserManager.getCurrentUrl());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Activity: emit command_end (skipped for chain subcommands)
|
// Activity: emit command_end (skipped for chain subcommands)
|
||||||
if (!opts?.skipActivity) {
|
if (!opts?.skipActivity) {
|
||||||
emitActivity({
|
emitActivity({
|
||||||
|
|||||||
Reference in New Issue
Block a user