fix(FN-4624): address review — gating, audit types, download/extract bugs, exports
Fusion-Task-Id: FN-4624 Fusion-Task-Lineage: 854e6033-bc17-45be-8d04-18bcb66b31c4
This commit is contained in:
committed by
gsxdsm
parent
616ee023a6
commit
f0dd4c55d5
@@ -1,6 +1,7 @@
|
||||
export { COLUMNS, DEFAULT_COLUMN, isColumn, normalizeColumn, COLUMN_LABELS, COLUMN_DESCRIPTIONS, VALID_TRANSITIONS, DEFAULT_SETTINGS, DEFAULT_GLOBAL_SETTINGS, DEFAULT_PROJECT_SETTINGS, GLOBAL_SETTINGS_KEYS, PROJECT_SETTINGS_KEYS, isGlobalSettingsKey, isProjectSettingsKey, resolvePersistAgentThinkingLog, THINKING_LEVELS, THEME_MODES, COLOR_THEMES, WORKFLOW_STEP_TEMPLATES, AGENT_PERMISSIONS, PERMANENT_AGENT_ACTION_CATEGORIES, AGENT_PERMISSION_POLICY_ACTION_CATEGORIES, AGENT_PROVISIONING_APPROVAL_MODES, AGENT_PERMISSION_POLICY_PRESET_IDS, LEGACY_AGENT_PERMISSION_POLICY_ACTION_CATEGORY_ALIASES, APPROVAL_REQUEST_STATUSES, APPROVAL_REQUEST_AUDIT_EVENT_TYPES, normalizeApprovalRequestActionCategory, isValidApprovalRequestTransition, agentToConfigSnapshot, diffConfigSnapshots, isEphemeralAgent, hasAgentIdentity, CheckoutConflictError, DEFAULT_HEARTBEAT_PROCEDURE_PATH, getDefaultHeartbeatProcedurePath, EXECUTION_MODES, DEFAULT_EXECUTION_MODE, TASK_PRIORITIES, DEFAULT_TASK_PRIORITY, HIGH_FANOUT_BLOCKER_TODO_THRESHOLD, STALE_HIGH_FANOUT_BLOCKER_AGE_THRESHOLD_MS, DASHBOARD_USER_ID, normalizeMessageParticipant, validateMessageMetadata, validateDockerNodeConfig, sanitizeDockerNodeConfigForResponse, normalizeMergeConflictStrategy, normalizeMergeStrategyOverlapBehavior, normalizePostMergeAuditMode, POST_MERGE_AUDIT_MODES, normalizeMergeAuditAutoRecovery, MERGE_AUDIT_AUTO_RECOVERY_MODES, normalizeAutoRecovery, AUTO_RECOVERY_MODES, buildResearchDocumentKey, REPO_OVERRIDE_RE, SHARED_STATE_SNAPSHOT_VERSION } from "./types.js";
|
||||
export type { Column, IssueInfo, IssueState, TaskSourceIssue, PrInfo, PrStatus, Task, TaskTokenUsage, TaskAttachment, TaskComment, TaskCommentInput, TaskDocument, TaskDocumentRevision, TaskDocumentCreateInput, TaskDocumentWithTask, TaskCreateInput, MeshReplicatedTaskCreatePayload, MeshReplicatedTaskApplyResult, TaskSource, SourceType, TaskDetail, RetrySummary, InboxTask, TodoList, TodoItem, TodoListCreateInput, TodoListUpdateInput, TodoItemCreateInput, TodoItemUpdateInput, TodoListWithItems, AgentLogEntry, AgentLogType, AgentRole, BoardConfig, DistributedTaskIdReserveInput, DistributedTaskIdReserveResult, DistributedTaskIdCommitInput, DistributedTaskIdCommitResult, DistributedTaskIdAbortInput, DistributedTaskIdAbortResult, DistributedTaskIdStateInput, DistributedTaskIdStateResult, AutostashOrphanRecord, AutostashOutcome, MergeDetails, MergeResult, MergeConflictStrategy, CanonicalMergeConflictStrategy, MergeStrategyOverlapBehavior, PostMergeAuditMode, MergeAuditAutoRecoveryMode, AutoRecoveryMode, AutoRecoveryFailureClass, AutoRecoverySettings, DirectMergeCommitStrategy, Settings, GlobalSettings, ProjectSettings, WebSearchBackend, ResearchEnabledSources, ResearchGlobalDefaults, ResearchProjectLimits, ResearchProjectSettings, SandboxBackendName, SandboxFailureMode, SandboxPolicy, SandboxProjectSettings, EvalFollowUpPolicy, EvalProjectSettings, ResolvedEvalSettings, SettingsScope, DaemonTokenSettings, TaskStep, StepStatus, TaskLogEntry, RunMutationContext, ActivityLogEntry, ActivityEventType, ThinkingLevel, ThemeMode, ColorTheme, ExecutionMode, TaskPriority, UnavailableNodePolicy, PlanningQuestion, PlanningSummary, PlanningResponse, PlanningQuestionType, ArchivedTaskEntry, BatchStatusRequest, BatchStatusResponse, BatchStatusEntry, BatchStatusResult, GithubIssueAction, ModelPreset, WorkflowStep, WorkflowStepMode, WorkflowStepGateMode, WorkflowStepPhase, WorkflowStepInput, WorkflowStepResult, WorkflowStepTemplate, Agent, OrgTreeNode, AgentState, AgentDetail, AgentCreateInput, AgentUpdateInput, AgentApiKey, AgentApiKeyCreateResult, AgentCapability, AgentPromptTemplate, AgentPromptsConfig, AgentPermission, PermanentAgentActionCategory, PermanentAgentSensitiveActionCategory, PermanentAgentGatingContext, AgentPermissionPolicy, AgentPermissionPolicyRules, AgentPermissionPolicyActionCategory, AgentProvisioningApprovalMode, LegacyAgentPermissionPolicyActionCategory, ApprovalRequestActionCategoryInput, ApprovalRequestActionCategory, AgentPermissionPolicyDisposition, AgentPermissionPolicyPresetId, ApprovalRequestStatus, ApprovalRequestAuditEventType, ApprovalRequestActorSnapshot, ApprovalRequestTargetAction, ApprovalRequestAuditEvent, ApprovalRequest, ApprovalRequestCreateInput, ApprovalRequestDecisionInput, ApprovalRequestCompletionInput, ApprovalRequestListInput, TaskAssignSource, AgentAccessState, AgentHeartbeatConfig, AgentBudgetConfig, AgentBudgetStatus, InstructionsBundleConfig, MessageResponseMode, AgentHeartbeatEvent, AgentHeartbeatRun, BlockedStateSnapshot, HeartbeatInvocationSource, AgentTaskSession, AgentRating, AgentRatingSummary, AgentRatingInput, AgentConfigSnapshot, RevisionFieldDiff, AgentConfigRevision, AgentStats, ReflectionTrigger, ReflectionMetrics, AgentReflection, AgentPerformanceSummary, NtfyNotificationEvent, NotificationEvent, NotificationPayload, NotificationProviderConfig, CustomProvider, SteeringComment, ParticipantType, MessageType, Message, MessageCreateInput, MessageFilter, MessageMetadata, MessageReplyReference, Mailbox, CheckoutLease, RunAuditDomain, RunAuditEvent, RunAuditEventInput, RunAuditEventFilter, AgentMemoryInclusionMode, HeartbeatPromptTemplate, HeartbeatScopeDisciplineMode } from "./types.js";
|
||||
export type { Column, IssueInfo, IssueState, TaskSourceIssue, PrInfo, PrStatus, Task, TaskTokenUsage, TaskAttachment, TaskComment, TaskCommentInput, TaskDocument, TaskDocumentRevision, TaskDocumentCreateInput, TaskDocumentWithTask, TaskCreateInput, MeshReplicatedTaskCreatePayload, MeshReplicatedTaskApplyResult, TaskSource, SourceType, TaskDetail, RetrySummary, InboxTask, TodoList, TodoItem, TodoListCreateInput, TodoListUpdateInput, TodoItemCreateInput, TodoItemUpdateInput, TodoListWithItems, AgentLogEntry, AgentLogType, AgentRole, BoardConfig, DistributedTaskIdReserveInput, DistributedTaskIdReserveResult, DistributedTaskIdCommitInput, DistributedTaskIdCommitResult, DistributedTaskIdAbortInput, DistributedTaskIdAbortResult, DistributedTaskIdStateInput, DistributedTaskIdStateResult, AutostashOrphanRecord, AutostashOutcome, MergeDetails, MergeResult, MergeConflictStrategy, CanonicalMergeConflictStrategy, MergeStrategyOverlapBehavior, PostMergeAuditMode, MergeAuditAutoRecoveryMode, AutoRecoveryMode, AutoRecoveryFailureClass, AutoRecoverySettings, DirectMergeCommitStrategy, Settings, GlobalSettings, ProjectSettings, WebSearchBackend, ResearchEnabledSources, ResearchGlobalDefaults, ResearchProjectLimits, ResearchProjectSettings, SandboxBackendName, SandboxFailureMode, SandboxPolicy, SandboxProjectSettings, EvalFollowUpPolicy, EvalProjectSettings, ResolvedEvalSettings, SettingsScope, DaemonTokenSettings, TaskStep, StepStatus, TaskLogEntry, RunMutationContext, ActivityLogEntry, ActivityEventType, ThinkingLevel, ThemeMode, ColorTheme, ExecutionMode, TaskPriority, UnavailableNodePolicy, PlanningQuestion, PlanningSummary, PlanningResponse, PlanningQuestionType, ArchivedTaskEntry, BatchStatusRequest, BatchStatusResponse, BatchStatusEntry, BatchStatusResult, GithubIssueAction, ModelPreset, WorkflowStep, WorkflowStepMode, WorkflowStepGateMode, WorkflowStepPhase, WorkflowStepInput, WorkflowStepResult, WorkflowStepTemplate, Agent, OrgTreeNode, AgentState, AgentDetail, AgentCreateInput, AgentUpdateInput, AgentApiKey, AgentApiKeyCreateResult, AgentCapability, AgentPromptTemplate, AgentPromptsConfig, AgentPermission, PermanentAgentActionCategory, PermanentAgentSensitiveActionCategory, PermanentAgentGatingContext, AgentPermissionPolicy, AgentPermissionPolicyRules, AgentPermissionPolicyActionCategory, AgentProvisioningApprovalMode, LegacyAgentPermissionPolicyActionCategory, ApprovalRequestActionCategoryInput, ApprovalRequestActionCategory, AgentPermissionPolicyDisposition, AgentPermissionPolicyPresetId, ApprovalRequestStatus, ApprovalRequestAuditEventType, ApprovalRequestActorSnapshot, ApprovalRequestTargetAction, ApprovalRequestAuditEvent, ApprovalRequest, ApprovalRequestCreateInput, ApprovalRequestDecisionInput, ApprovalRequestCompletionInput, ApprovalRequestListInput, TaskAssignSource, AgentAccessState, AgentHeartbeatConfig, AgentBudgetConfig, AgentBudgetStatus, InstructionsBundleConfig, MessageResponseMode, AgentHeartbeatEvent, AgentHeartbeatRun, BlockedStateSnapshot, HeartbeatInvocationSource, AgentTaskSession, AgentRating, AgentRatingSummary, AgentRatingInput, AgentConfigSnapshot, RevisionFieldDiff, AgentConfigRevision, AgentStats, ReflectionTrigger, ReflectionMetrics, AgentReflection, AgentPerformanceSummary, NtfyNotificationEvent, NotificationEvent, NotificationPayload, NotificationProviderConfig, CustomProvider, SteeringComment, ParticipantType, MessageType, Message, MessageCreateInput, MessageFilter, MessageMetadata, MessageReplyReference, Mailbox, CheckoutLease, RunAuditDomain, RunAuditEvent, RunAuditEventInput, RunAuditEventFilter, AgentMemoryInclusionMode, HeartbeatPromptTemplate, HeartbeatScopeDisciplineMode, WorktrunkSettings, WorktrunkOnFailure } from "./types.js";
|
||||
export { AGENT_VALID_TRANSITIONS } from "./types.js";
|
||||
export { resolveWorktrunkSettings, validateWorktrunkSettings, DEFAULT_WORKTRUNK_SETTINGS } from "./worktrunk-settings.js";
|
||||
export {
|
||||
resolveAgentMemoryInclusionMode,
|
||||
type AgentMemoryInclusionModeSource,
|
||||
|
||||
@@ -65,11 +65,13 @@ export const NETWORK_API_TOOLS: ReadonlySet<string> = new Set([
|
||||
"fn_research_cancel",
|
||||
"fn_research_retry",
|
||||
"fn_web_fetch", // FN-4603: outbound HTTP fetch should be network-classified.
|
||||
"worktrunk_install", // FN-4624: binary auto-install downloads from GitHub.
|
||||
]);
|
||||
|
||||
export const ACTION_GATE_NETWORK_API_TOOLS: ReadonlySet<string> = new Set([
|
||||
"fn_research_run",
|
||||
"fn_web_fetch", // FN-4603: honor network_api approval policy for web fetches.
|
||||
"worktrunk_install", // FN-4624: gate binary auto-install under network_api policy.
|
||||
]);
|
||||
|
||||
export const READONLY_FN_TOOLS: ReadonlySet<string> = new Set([
|
||||
|
||||
@@ -112,6 +112,22 @@ export { generateReservedWorktreeName, generateWorktreeName, planTaskWorktreePat
|
||||
export { createLogger, type Logger } from "./logger.js";
|
||||
export { fetchWebContent, assertSafeUrl, WebFetchError, type WebFetchOptions, type WebFetchResult, type WebFetchErrorCode } from "./web-fetch.js";
|
||||
export { classifyTaskError, type ErrorClass, type TaskErrorClassification } from "./error-classifier.js";
|
||||
export {
|
||||
resolveWorktrunkBinary,
|
||||
installWorktrunk,
|
||||
probeWorktrunk,
|
||||
clearWorktrunkResolveCache,
|
||||
WorktrunkBinaryUnavailableError,
|
||||
WorktrunkInstallDeniedError,
|
||||
WorktrunkInstallFailedError,
|
||||
WORKTRUNK_PINNED_RELEASE,
|
||||
WORKTRUNK_INSTALL_DIR,
|
||||
WORKTRUNK_INSTALL_PATH,
|
||||
WORKTRUNK_PROBE_TIMEOUT_MS,
|
||||
WORKTRUNK_DOWNLOAD_TIMEOUT_MS,
|
||||
WORKTRUNK_DOWNLOAD_MAX_BYTES,
|
||||
WORKTRUNK_CARGO_TIMEOUT_MS,
|
||||
} from "./worktrunk-installer.js";
|
||||
export { isUsageLimitError, UsageLimitPauser } from "./usage-limit-detector.js";
|
||||
export { withRateLimitRetry } from "./rate-limit-retry.js";
|
||||
export { ResearchOrchestrator, type ResearchOrchestratorOptions, type ResearchOrchestratorStatus, type ResearchOrchestratorStartOptions } from "./research-orchestrator.js";
|
||||
|
||||
@@ -143,7 +143,11 @@ export type FilesystemMutationType =
|
||||
| "prompt:write"
|
||||
| "prompt:update"
|
||||
| "session:write"
|
||||
| "session:delete";
|
||||
| "session:delete"
|
||||
| "binary:install-requested"
|
||||
| "binary:install-success"
|
||||
| "binary:install-failed"
|
||||
| "binary:install-denied";
|
||||
|
||||
export type SandboxMutationType = "sandbox:prepare" | "sandbox:run" | "sandbox:failure" | "sandbox:fallback";
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import fs from "node:fs/promises";
|
||||
import https from "node:https";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { pipeline } from "node:stream/promises";
|
||||
import { PassThrough } from "node:stream";
|
||||
import { promisify } from "node:util";
|
||||
import type { AgentPermissionPolicy, WorktrunkSettings } from "@fusion/core";
|
||||
import { evaluateAgentActionGate, type AgentActionGateContext } from "./agent-action-gate.js";
|
||||
@@ -187,14 +187,14 @@ async function applyInstallGate(opts: {
|
||||
|
||||
async function downloadReleaseAsset(url: string, targetPath: string): Promise<string> {
|
||||
await assertSafeUrl(url, false);
|
||||
const hash = createHash("sha256");
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
const req = https.get(url, (res) => {
|
||||
const statusCode = res.statusCode ?? 0;
|
||||
if (statusCode >= 300 && statusCode < 400 && res.headers.location) {
|
||||
req.destroy();
|
||||
downloadReleaseAsset(new URL(res.headers.location, url).toString(), targetPath).then(() => resolve()).catch(reject);
|
||||
downloadReleaseAsset(new URL(res.headers.location, url).toString(), targetPath)
|
||||
.then((hash) => resolve(hash))
|
||||
.catch(reject);
|
||||
return;
|
||||
}
|
||||
if (statusCode < 200 || statusCode >= 300) {
|
||||
@@ -202,28 +202,56 @@ async function downloadReleaseAsset(url: string, targetPath: string): Promise<st
|
||||
return;
|
||||
}
|
||||
|
||||
const hash = createHash("sha256");
|
||||
const file = createWriteStream(targetPath);
|
||||
let bytes = 0;
|
||||
res.on("data", (chunk: Buffer) => {
|
||||
let sizeExceeded = false;
|
||||
|
||||
const meter = new PassThrough();
|
||||
meter.on("data", (chunk: Buffer) => {
|
||||
bytes += chunk.length;
|
||||
if (bytes > WORKTRUNK_DOWNLOAD_MAX_BYTES) {
|
||||
sizeExceeded = true;
|
||||
req.destroy(new Error("download exceeded size cap"));
|
||||
return;
|
||||
}
|
||||
hash.update(chunk);
|
||||
});
|
||||
|
||||
pipeline(res, createWriteStream(targetPath)).then(resolve).catch(reject);
|
||||
// Hash transform: updates sha256 as data passes through.
|
||||
const hashTransform = new PassThrough();
|
||||
hashTransform.on("data", (chunk: Buffer) => hash.update(chunk));
|
||||
|
||||
// Pipe: res -> meter -> hashTransform -> file
|
||||
// meter tracks bytes; hashTransform feeds sha256; file writes to disk.
|
||||
meter.pipe(hashTransform).pipe(file);
|
||||
|
||||
// Feed the response into the meter
|
||||
res.on("data", (chunk: Buffer) => meter.write(chunk));
|
||||
res.on("end", () => meter.end());
|
||||
res.on("error", (err) => { meter.destroy(err); });
|
||||
|
||||
file.on("finish", () => {
|
||||
if (sizeExceeded) {
|
||||
reject(new Error("download exceeded size cap"));
|
||||
} else {
|
||||
resolve(hash.digest("hex"));
|
||||
}
|
||||
});
|
||||
file.on("error", reject);
|
||||
meter.on("error", reject);
|
||||
});
|
||||
|
||||
req.setTimeout(WORKTRUNK_DOWNLOAD_TIMEOUT_MS, () => req.destroy(new Error("download timed out")));
|
||||
req.setTimeout(WORKTRUNK_DOWNLOAD_TIMEOUT_MS, () =>
|
||||
req.destroy(new Error("download timed out")),
|
||||
);
|
||||
req.on("error", reject);
|
||||
});
|
||||
|
||||
return hash.digest("hex");
|
||||
}
|
||||
|
||||
async function extractAsset(archivePath: string, innerBinaryName: string, targetPath: string): Promise<void> {
|
||||
if (archivePath.endsWith(".tar.gz")) {
|
||||
// NOTE: tar.gz archives extracted via system `tar`, .zip via system `unzip`.
|
||||
// No heavy archive dependencies bundled; relies on POSIX tooling.
|
||||
const name = path.basename(archivePath, ".download");
|
||||
if (name.endsWith(".tar.gz")) {
|
||||
await execAsync(`tar -xzf "${archivePath}" -O "${innerBinaryName}" > "${targetPath}"`, {
|
||||
timeout: WORKTRUNK_PROBE_TIMEOUT_MS,
|
||||
maxBuffer: WORKTRUNK_DOWNLOAD_MAX_BYTES,
|
||||
@@ -231,7 +259,7 @@ async function extractAsset(archivePath: string, innerBinaryName: string, target
|
||||
return;
|
||||
}
|
||||
|
||||
if (archivePath.endsWith(".zip")) {
|
||||
if (name.endsWith(".zip")) {
|
||||
await execAsync(`unzip -p "${archivePath}" "${innerBinaryName}" > "${targetPath}"`, {
|
||||
timeout: WORKTRUNK_PROBE_TIMEOUT_MS,
|
||||
maxBuffer: WORKTRUNK_DOWNLOAD_MAX_BYTES,
|
||||
|
||||
Reference in New Issue
Block a user