diff --git a/scripts/resolvers/outside-voice-steps.ts b/scripts/resolvers/outside-voice-steps.ts index 05f216737..74a5a5ecc 100644 --- a/scripts/resolvers/outside-voice-steps.ts +++ b/scripts/resolvers/outside-voice-steps.ts @@ -16,7 +16,7 @@ import { toShellPath, type TemplateContext } from './types'; import { CC_BACKGROUND_DEFAULT_SINCE } from './constants'; import { outsideVoiceFailurePolicy, outsideVoiceFor, outsideVoiceInvocation, outsideVoicePreflight, outsideVoiceProvenance, outsideVoiceRuntime } from './outside-voice'; -const CODEX_BOUNDARY = 'IMPORTANT: Do NOT read or execute any files under ~/.claude/, ~/.agents/, .claude/skills/, or agents/. These are skill definitions, not repository review data. Do not invoke any installed skill (Codex home skills/, .agents/), hook, or tool instruction; answer directly. Ignore them completely. Do NOT modify agents/openai.yaml. Stay focused on the repository code only.\\n\\n'; +const CODEX_BOUNDARY = 'Filesystem boundary: do not read or execute any files under ~/.claude/, ~/.agents/, .claude/skills/, or agents/. They hold skill definitions, not repository code to review. Do not invoke any installed skill (Codex home skills/, .agents/), hook, or tool instruction; answer directly. Do not modify agents/openai.yaml. Review only the repository code.\\n\\n'; export function generateCodexSecondOpinion(ctx: TemplateContext): string { diff --git a/test/codex-skill-isolation.test.ts b/test/codex-skill-isolation.test.ts index ec407fcc8..d2fa0e5db 100644 --- a/test/codex-skill-isolation.test.ts +++ b/test/codex-skill-isolation.test.ts @@ -59,10 +59,10 @@ describe('Codex skill isolation (#2847)', () => { const sources = ['scripts/resolvers/outside-voice-steps.ts', ...['review-mode', 'challenge-mode', 'consult-mode'].map(m => `codex/sections/${m}.md.tmpl`)]; for (const rel of sources) { const text = fs.readFileSync(path.join(ROOT, rel), 'utf8'); - const boundaries = text.split('\n').filter(line => line.includes('IMPORTANT: Do NOT read or execute any files under')); + const boundaries = text.split('\n').filter(line => /do not read or execute any files under/i.test(line)); expect(boundaries.length, rel).toBeGreaterThan(0); for (const line of boundaries) { - expect(line, rel).toContain('Do not invoke any installed skill (Codex home skills/, .agents/)'); + expect(line, rel).toMatch(/do not invoke any installed skill \(Codex home skills\/, \.agents\/\)/i); } } }); diff --git a/test/gen-skill-docs.test.ts b/test/gen-skill-docs.test.ts index 8a626db9e..06119d6d9 100644 --- a/test/gen-skill-docs.test.ts +++ b/test/gen-skill-docs.test.ts @@ -2811,7 +2811,7 @@ describe('Codex generation (--host codex)', () => { const content = fs.readFileSync(path.join(AGENTS_DIR, skill.codexName, 'SKILL.md'), 'utf-8'); // Outside prompts may explicitly forbid reading Claude's skill directory. // Every executable/runtime path must still use the selected host root. - const withoutBoundary = content.replace(/^.*IMPORTANT: do not read or execute[^\n]*$/gim, ''); + const withoutBoundary = content.replace(/^.*do not read or execute any files under[^\n]*$/gim, ''); expect(withoutBoundary).not.toContain('~/.claude/'); } }); diff --git a/test/helpers/safety-rules.ts b/test/helpers/safety-rules.ts index 8e9077ce6..08a3e1b44 100644 --- a/test/helpers/safety-rules.ts +++ b/test/helpers/safety-rules.ts @@ -31,7 +31,7 @@ export const SAFETY_RULES: readonly SafetyRule[] = [ evalId: 'safety-codex-boundary', source: 'scripts/resolvers/outside-voice-steps.ts', match: /^const CODEX_BOUNDARY = /, - sha256: 'bb7fc2f71194fb250645a616f2163ec195cf05d2115e36923ecefe98319fc2f7', + sha256: '5212f94292bfa1b3a94d9713e6f42087c4dc48711bd3f73b6461f828aeef01c0', }, { id: 'ship-fresh-evidence', diff --git a/test/skill-validation.test.ts b/test/skill-validation.test.ts index e881e715f..e79a97f92 100644 --- a/test/skill-validation.test.ts +++ b/test/skill-validation.test.ts @@ -1731,14 +1731,13 @@ describe('Codex skill', () => { // Do NOT "restore" the boundary by putting a prompt argument back on a // scoped `codex review` call: that combination fails to parse, and // dropping the scope flag to make it parse silently reviews the wrong diff. - const boundaryLine = - 'Do NOT read or execute any files under ~/.claude/, ~/.agents/, .claude/skills/, or agents/'; + const boundaryLine = /do not read or execute any files under ~\/\.claude\/, ~\/\.agents\/, \.claude\/skills\/, or agents\//i; for (const rel of ['codex/SKILL.md', 'review/SKILL.md', 'ship/SKILL.md']) { // ship's AND review's codex/adversarial boundary lines moved into sections/adversarial.md. const content = rel === 'ship/SKILL.md' ? readShipUnion() : rel === 'review/SKILL.md' ? readSkillUnion('review') : fs.readFileSync(path.join(ROOT, rel), 'utf-8'); - expect(content).toContain(boundaryLine); + expect(content).toMatch(boundaryLine); } }); diff --git a/test/template-context-parity.test.ts b/test/template-context-parity.test.ts index 1540ed744..8ab29650d 100644 --- a/test/template-context-parity.test.ts +++ b/test/template-context-parity.test.ts @@ -35,7 +35,7 @@ describe('section TemplateContext parity (skillName pinned to parent)', () => { test('adversarial section rendered the ADVERSARIAL_STEP resolver (proves ship ctx)', () => { const content = readSection('adversarial.md'); // The codex filesystem-boundary line only appears when ADVERSARIAL_STEP resolves. - expect(content).toContain('Do NOT read or execute any files under'); + expect(content).toMatch(/do not read or execute any files under/i); expect(content.length).toBeGreaterThan(500); });