@@ -671,4 +671,63 @@ describe("config io audit helpers", () => {
671671 expect ( after ) . toContain ( "xoxb-real-bot-token" ) ;
672672 expect ( fs . existsSync ( `${ auditPath } .scrub.tmp` ) ) . toBe ( false ) ;
673673 } ) ;
674+
675+ it ( "aborts without overwriting when the audit log is appended to after temp write" , async ( ) => {
676+ const home = await suiteRootTracker . make ( "scrub-race-after-temp-write" ) ;
677+ const auditPath = path . join ( home , ".openclaw" , "logs" , "config-audit.jsonl" ) ;
678+ fs . mkdirSync ( path . dirname ( auditPath ) , { recursive : true , mode : 0o700 } ) ;
679+ const unredacted = {
680+ ts : "2026-05-02T00:03:48.471Z" ,
681+ argv : [
682+ "node" ,
683+ "openclaw.mjs" ,
684+ "config" ,
685+ "set" ,
686+ "channels.slack.botToken" ,
687+ "xoxb-real-bot-token-1234567890abcdef0123456789abcdef" ,
688+ ] ,
689+ execArgv : [ ] ,
690+ } ;
691+ const appended = {
692+ ts : "2026-05-02T00:04:00.000Z" ,
693+ argv : [ "node" , "openclaw.mjs" , "config" , "set" , "theme" , "dark" ] ,
694+ execArgv : [ ] ,
695+ } ;
696+ const original = `${ JSON . stringify ( unredacted ) } \n` ;
697+ const appendedLine = `${ JSON . stringify ( appended ) } \n` ;
698+ fs . writeFileSync ( auditPath , original , { encoding : "utf-8" , mode : 0o600 } ) ;
699+ let renameCalled = false ;
700+
701+ const raceFs = {
702+ promises : {
703+ readFile : fsPromises . readFile ,
704+ stat : fsPromises . stat ,
705+ writeFile : async (
706+ p : string ,
707+ data : string ,
708+ options ?: { encoding ?: BufferEncoding ; mode ?: number } ,
709+ ) => {
710+ await fsPromises . writeFile ( p , data , options ) ;
711+ await fsPromises . appendFile ( auditPath , appendedLine , "utf-8" ) ;
712+ } ,
713+ rename : async ( ) => {
714+ renameCalled = true ;
715+ } ,
716+ unlink : fsPromises . unlink ,
717+ } ,
718+ } ;
719+ const result = await scrubConfigAuditLog ( {
720+ fs : raceFs ,
721+ env : { } as NodeJS . ProcessEnv ,
722+ homedir : ( ) => home ,
723+ } ) ;
724+
725+ expect ( result . aborted ) . toBe ( true ) ;
726+ expect ( result . rewritten ) . toBeGreaterThan ( 0 ) ;
727+ expect ( renameCalled ) . toBe ( false ) ;
728+ const after = fs . readFileSync ( auditPath , "utf-8" ) ;
729+ expect ( after ) . toBe ( `${ original } ${ appendedLine } ` ) ;
730+ expect ( after ) . toContain ( "xoxb-real-bot-token" ) ;
731+ expect ( fs . existsSync ( `${ auditPath } .scrub.tmp` ) ) . toBe ( false ) ;
732+ } ) ;
674733} ) ;
0 commit comments