T09 · Insecure Skill Coding Practices
Warning
- Location
- references/compaction-rpc.ts:76
- Finding
- Compaction Summaries Persisted in Plaintext Without Explicit Restrictive File Permissions<![CDATA[ ## Vulnerability Details **File Location**: `references/compaction-rpc.ts`, lines 76-98 **Vulnerability Type**: Plaintext storage of sensitive conversation data with insufficiently explicit file permissions **Risk Level**: Medium ### Vulnerable Code ```ts async function saveCompactionConfig(config: CompactionConfigFile, agentDir?: string): Promise<void> { const configPath = getConfigPath(agentDir); await fs.mkdir(path.dirname(configPath), { recursive: true }); await fs.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8"); } /** * Called by the compaction engine to record a result. * Exported for use by the sessions.compact RPC and auto-compaction trigger. */ export async function recordCompactionResult( result: CompactionLastResult, agentDir?: string, ): Promise<void> { const config = await loadCompactionConfig(agentDir); if (config.settings.storeLastResult) { config.lastResult = result; } else { // Still store metadata, just not the summary config.lastResult = { ...result, summary: undefined }; } await saveCompactionConfig(config, agentDir); } ``` ### Technical Analysis When result storage is enabled, `result.summary` can contain a condensed representation of the user's conversation, including confidential prompts, personal information, source code, operational details, or credentials accidentally included in chat history. The summary is serialized directly into `{agentDir}/compaction-config.json` as plaintext. The `fs.writeFile` call does not specify a restrictive mode such as `0o600`, so permissions are determined by the process umask and any permissions already present on the file. In a permissively configured or multi-user environment, the resulting file may be readable by unintended local users or processes. The write is also performed directly against the destination rather than through an atomic temporary-file replacement. An interruption could leave a partially written configuration file, ...[truncated 1721 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Create and maintain the configuration file with owner-only permissions: ```ts await fs.writeFile(configPath, JSON.stringify(config, null, 2), { encoding: "utf-8", mode: 0o600, }); await fs.chmod(configPath, 0o600); ``` 2. Ensure the containing agent directory is restricted to the owning user, preferably with mode `0o700`. 3. Use atomic writes: - Write to a temporary file in the same restricted directory. - Set mode `0o600` on the temporary file. - Flush the file if durability is required. - Atomically rename it over the destination. 4. Clearly disclose in the UI that stored summaries are persisted locally in plaintext and may contain sensitive conversation content. 5. Consider encrypting stored summaries with a key managed separately from the configuration file. 6. Apply retention controls, such as automatic expiration, and clear previously stored summary text immediately when result storage is disabled. 7. Consider redacting common credential formats and other high-risk secrets before persistence, while warning that automated redaction cannot guarantee complete removal. ]]>
