33 *
44 * These functions perform I/O (filesystem, config reads) to detect security issues.
55 */
6- import fs from "node:fs/promises" ;
76import path from "node:path" ;
87import { clearTimeout as clearNodeTimeout , setTimeout as setNodeTimeout } from "node:timers" ;
98import {
@@ -21,6 +20,7 @@ import { MANIFEST_KEY } from "../compat/legacy-names.js";
2120import type { OpenClawConfig , ConfigFileSnapshot } from "../config/config.js" ;
2221import { collectIncludePathsRecursive } from "../config/includes-scan.js" ;
2322import { resolveOAuthDir } from "../config/paths.js" ;
23+ import { readRegularFile , statRegularFile } from "../infra/fs-safe.js" ;
2424import { normalizeAgentId } from "../routing/session-key.js" ;
2525import { getOrCreatePromise } from "../shared/lazy-promise.js" ;
2626import { createLazyRuntimeModule , createLazyRuntimeNamedExport } from "../shared/lazy-runtime.js" ;
@@ -97,9 +97,29 @@ function expandTilde(p: string, env: NodeJS.ProcessEnv): string | null {
9797 return null ;
9898}
9999
100+ const MAX_PLUGIN_MANIFEST_BYTES = 1024 * 1024 ;
101+ // Skill file audit reads are bounded like other audit reads; matches the
102+ // workspace loader's DEFAULT_MAX_SKILL_FILE_BYTES so oversized SKILL.md files
103+ // cannot force an unbounded read during the code-safety scan.
104+ const MAX_SKILL_AUDIT_FILE_BYTES = 256_000 ;
105+
100106async function readPluginManifestExtensions ( pluginPath : string ) : Promise < string [ ] > {
101107 const manifestPath = path . join ( pluginPath , "package.json" ) ;
102- const raw = await fs . readFile ( manifestPath , "utf-8" ) . catch ( ( ) => "" ) ;
108+ const statResult = await statRegularFile ( manifestPath ) ;
109+ if ( statResult . missing ) {
110+ return [ ] ;
111+ }
112+ if ( statResult . stat . size > MAX_PLUGIN_MANIFEST_BYTES ) {
113+ throw new Error (
114+ `Plugin manifest at ${ manifestPath } is too large (${ statResult . stat . size } bytes, max ${ MAX_PLUGIN_MANIFEST_BYTES } )` ,
115+ ) ;
116+ }
117+
118+ const { buffer } = await readRegularFile ( {
119+ filePath : manifestPath ,
120+ maxBytes : MAX_PLUGIN_MANIFEST_BYTES ,
121+ } ) ;
122+ const raw = buffer . toString ( "utf-8" ) ;
103123 if ( ! raw . trim ( ) ) {
104124 return [ ] ;
105125 }
@@ -177,7 +197,10 @@ async function getSkillCodeSafetySummary(params: {
177197 dirPath : params . dirPath ,
178198 summaryCache : params . summaryCache ,
179199 } ) ,
180- fs . readFile ( params . skillFilePath , "utf-8" ) ,
200+ readRegularFile ( {
201+ filePath : params . skillFilePath ,
202+ maxBytes : MAX_SKILL_AUDIT_FILE_BYTES ,
203+ } ) . then ( ( { buffer } ) => buffer . toString ( "utf-8" ) ) ,
181204 loadSkillScannerModule ( ) ,
182205 ] ) ;
183206 const skillFindings = [
0 commit comments