assistant/tools/learning/test-navigation-profile.mjs

644 lines
46 KiB
JavaScript

// Synthetic contracts and a loopback-only DOM fixture. Official-looking links
// are inspected but never clicked, fetched, or exposed as an origin override.
import assert from 'node:assert/strict';
import fs from 'node:fs/promises';
import path from 'node:path';
import {fileURLToPath} from 'node:url';
import {createServer} from 'node:http';
import {ActionInputError} from './action-contract.mjs';
const root=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'../..');
await fs.mkdir(path.join(root,'.tmp'),{recursive:true});
const output=await fs.mkdtemp(path.join(root,'.tmp','navigation-profile-'));
process.env.ASSISTANT_LEARNING_HOME=path.join(output,'worker-learning');
process.env.ASSISTANT_BROWSER_HEADLESS='1';
process.env.ASSISTANT_BROWSER_CHANNEL=process.platform==='win32' ? 'msedge' : 'chromium';
let assertions=0;
const results=[];
let browserFixture=null;
const eq=(actual,expected,message)=>{assert.deepEqual(actual,expected,message);assertions+=1;};
const ok=(value,message)=>{assert.ok(value,message);assertions+=1;};
const throws=(fn,code)=>{assert.throws(fn,error=>error instanceof ActionInputError && error.code===code);assertions+=1;};
const report=async(extra={})=>fs.writeFile(path.join(output,'report.json'),JSON.stringify({
result:'passed',passed:results.length,assertions,results,official_browser_navigation:false,
live_platform_requests:0,application_api_calls:0,model_calls:0,
browser_fixture:browserFixture || 'not executed',...extra,
},null,2));
const group=async(name,run)=>{
const start=assertions;
try{const details=await run();results.push({name,assertions:assertions-start,...details});await report();}
catch(error){await report({result:'failed',failed_group:name,error:{name:error.name,message:error.message,code:error.code}});throw error;}
};
const official='https://www.xiaohongshu.com';
const noteId='a'.repeat(24),otherId='b'.repeat(24),profileId='c'.repeat(24);
const note=`${official}/explore/${noteId}?xsec_token=LOCAL_NAVIGATION_FIXTURE`;
const profileUrl=`${official}/user/profile/${profileId}?source=LOCAL_FIXTURE`;
const profile=await import('./execution-profile.mjs');
const {NATIVE_UI_PROFILE,NATIVE_UI_CONTRACT,NAVIGATION_UI_PROFILE,NAVIGATION_UI_CONTRACT,
isNativeUiProfile,resolveExecutionProfile,executionContractMatches,executionMetadata,
validNativePage,validNavigationPage,navigationTarget,navigationPopupMatches,navigationComparison,assertNativeUiAction,assertNativeUiResultLocation}=profile;
await group('navigation profile is explicit immutable and never silently changes the old profile',()=>{
eq(NATIVE_UI_PROFILE,'playwright-xhs-native-ui-v1');
eq(NATIVE_UI_CONTRACT,{engine:'playwright',action_policy:'xhs-bounded-readonly-v1',network_policy:'xhs-page-native-v1'});
eq(NAVIGATION_UI_PROFILE,'playwright-xhs-navigation-ui-v1');
eq(NAVIGATION_UI_CONTRACT,{engine:'playwright',action_policy:'xhs-observed-navigation-readonly-v1',network_policy:'xhs-page-navigation-v1'});
eq(Object.isFrozen(NATIVE_UI_CONTRACT),true);eq(Object.isFrozen(NAVIGATION_UI_CONTRACT),true);
eq(resolveExecutionProfile(undefined),null);
eq(resolveExecutionProfile(NATIVE_UI_PROFILE),NATIVE_UI_CONTRACT);
eq(resolveExecutionProfile(NAVIGATION_UI_PROFILE),NAVIGATION_UI_CONTRACT);
eq(executionContractMatches({...NAVIGATION_UI_CONTRACT},NAVIGATION_UI_PROFILE),true);
eq(executionContractMatches({...NATIVE_UI_CONTRACT},NATIVE_UI_PROFILE),true);
eq(executionContractMatches({...NATIVE_UI_CONTRACT},NAVIGATION_UI_PROFILE),false);
eq(executionContractMatches({...NAVIGATION_UI_CONTRACT},NATIVE_UI_PROFILE),false);
for(const value of [null,undefined,[],{},Object.create(NAVIGATION_UI_CONTRACT),
{...NAVIGATION_UI_CONTRACT,extra:'fixture'}, {...NAVIGATION_UI_CONTRACT,engine:'browser-use'},
{...NAVIGATION_UI_CONTRACT,network_policy:NATIVE_UI_CONTRACT.network_policy}]){
eq(executionContractMatches(value,NAVIGATION_UI_PROFILE),false);
}
eq(isNativeUiProfile(NATIVE_UI_PROFILE),true);eq(isNativeUiProfile(NAVIGATION_UI_PROFILE),true);
for(const value of [null,undefined,false,0,'',{},'unknown']) eq(isNativeUiProfile(value),false);
eq(executionMetadata(undefined),{});
const metadata=executionMetadata(NAVIGATION_UI_PROFILE);
eq(metadata,{execution_profile:NAVIGATION_UI_PROFILE,execution_contract:NAVIGATION_UI_CONTRACT});
metadata.execution_contract.engine='LOCAL_MUTATION';
eq(executionMetadata(NAVIGATION_UI_PROFILE).execution_contract.engine,'playwright');
for(const value of [null,false,'',{},NAVIGATION_UI_PROFILE+' ',NAVIGATION_UI_PROFILE.toUpperCase(),'playwright-xhs-navigation-ui-v2']){
throws(()=>resolveExecutionProfile(value),'EXECUTION_PROFILE_INVALID');
}
});
await group('destination parser allows only observed official page shapes and keeps the exact href',()=>{
for(const [href,kind,objectId] of [[note,'note',noteId],[profileUrl,'profile',profileId],
[`${official}/search_result/${otherId}`,'note',otherId],[official+'/notification','notification',null],
[official+'/explore','explore',null],[official+'/search_result?keyword=fixture','search',null],
[official+'/search_result_ai?keyword=fixture','search',null]]){
eq(validNavigationPage(href),true,href);
eq(navigationTarget(href),{kind,object_id:objectId,href:new URL(href).href});
}
for(const href of [null,undefined,{},'', 'not a url','/explore',
'http://www.xiaohongshu.com/explore','https://xiaohongshu.com/explore',
'https://www.xiaohongshu.com:8443/explore','https://u:p@www.xiaohongshu.com/explore',
'https://www.xiaohongshu.com.evil.invalid/explore','https://www.xiaohongshu.com@evil.invalid/explore',
'https://so.xiaohongshu.com/explore',official+'/',official+'/explore/',
official+'/message',official+'/messages',official+'/notification/',official+'/notification/send',
official+'/user/profile/'+profileId+'/',official+'/user/profile/'+profileId.slice(1),
official+'/user/profile/'+'g'.repeat(24),official+'/user/profile/%2f'+profileId,
official+'/user/profile/'+profileId+'/follow',official+'/api/sns/web/v2/comment/post',
official+'/api/sns/web/v1/follow',official+'/explore/'+noteId+'/delete',
'javascript:void(0)','data:text/html,fixture','file:///fixture']){
eq(validNavigationPage(href),false,String(href));eq(navigationTarget(href),null);
}
for(const href of [profileUrl,official+'/notification']) eq(validNativePage(href),false,'old v1 page scope remains unchanged');
});
const observed={kind:'click',tag:'a',unique:true,sensitive:false};
const actual=(href,kind)=>({tag:'a',href,sensitive:false,visible:true,in_viewport:true,unobscured:true,
native_context:{search_input:false,note_link:kind==='note',profile_link:kind==='profile',navigation_link:['notification','explore','search'].includes(kind),read_root:false}});
const action=(href,kind,pageUrl=note)=>({executionProfile:NAVIGATION_UI_PROFILE,kind:'click',pageUrl,
selector:'#fixture',observed:{...observed},actualRoots:[actual(href,kind)],matchCount:1,href});
const reject=(context,code='WRITE_ACTION_BLOCKED')=>{
const before=JSON.stringify(context);throws(()=>assertNativeUiAction(context),code);eq(JSON.stringify(context),before);
};
await group('new navigation accepts fixed note profile notification and return links only',()=>{
for(const [href,kind] of [[profileUrl,'profile'],[official+'/notification','notification'],
[official+'/explore','explore'],[official+'/search_result?keyword=fixture','search'],[note,'note']]){
const context=action(href,kind);
eq(assertNativeUiAction(context),true);
eq(assertNativeUiAction({...context,pageUrl:profileUrl}),true);
eq(assertNativeUiAction({...context,pageUrl:official+'/notification'}),true);
eq(assertNativeUiResultLocation(context,href),true);
}
const collection=action(profileUrl,'profile');
reject({...collection,observed:{...observed,unique:false}});
eq(assertNativeUiAction({...collection,observed:{...observed,unique:false},targetResolvedUnique:true}),true);
reject({...collection,observed:{...observed,unique:false},targetResolvedUnique:true,matchCount:2});
});
await group('click permission requires actual usable unique immutable anchor evidence',()=>{
const context=action(profileUrl,'profile'),root=context.actualRoots[0];
for(const patch of [
{observed:null},{observed:{...observed,kind:'read'}},{observed:{...observed,tag:'button'}},
{observed:{...observed,sensitive:true}},{matchCount:0},{matchCount:2},{actualRoots:[]},
{actualRoots:[root,root]},{actualRoots:[{...root,sensitive:true}]},
{actualRoots:[{...root,visible:false}]},{actualRoots:[{...root,in_viewport:false}]},
{actualRoots:[{...root,unobscured:false}]},{actualRoots:[{...root,tag:'button'}]},
{actualRoots:[{...root,native_context:undefined}]},
{actualRoots:[{...root,native_context:{...root.native_context,profile_link:false}}]},
{actualRoots:[{...root,href:`${official}/user/profile/${otherId}`}]},
{href:`${official}/user/profile/${otherId}`},
]) reject({...context,...patch});
for(const tag of ['button','input','textarea','select','form']){
reject({...context,observed:{...observed,tag},actualRoots:[{...root,tag}]});
}
for(const kind of ['follow','unfollow','send_message','comment','evaluate','fetch','request','goto']){
reject({...context,kind},'SENSITIVE_OUTPUT');
}
});
await group('fixed profile cannot be borrowed by a note or arbitrary page link',()=>{
for(const [href,kind] of [[profileUrl,'note'],[note,'profile'],[profileUrl,'notification'],
[official+'/notification','profile'],[official+'/message','notification'],
[official+'/api/sns/web/v1/follow','profile'],['https://evil.invalid/user/profile/'+profileId,'profile'],
['https://www.xiaohongshu.com:8443/user/profile/'+profileId,'profile'],
['https://u:p@www.xiaohongshu.com/user/profile/'+profileId,'profile']]) reject(action(href,kind));
for(const executionProfile of [undefined,NATIVE_UI_PROFILE]){
const context=action(profileUrl,'profile',official+'/explore');
reject({...context,executionProfile});
reject({...action(official+'/notification','notification',official+'/explore'),executionProfile});
eq(assertNativeUiAction({...action(note,'note',official+'/explore'),executionProfile}),true);
}
for(const executionProfile of [null,'unknown',false,{},NAVIGATION_UI_PROFILE+' ']){
reject({...action(profileUrl,'profile'),executionProfile},'EXECUTION_PROFILE_INVALID');
}
});
await group('navigation result binds complete destination and object rather than a merely allowed page',()=>{
const context=action(profileUrl,'profile');
for(const changed of [note,context.pageUrl,official+'/notification',
`${official}/user/profile/${otherId}?source=LOCAL_FIXTURE`,
`${official}/user/profile/${profileId}`,profileUrl+'&changed=1',profileUrl+'#changed']){
throws(()=>assertNativeUiResultLocation(context,changed),'NAVIGATION_BINDING_MISMATCH');
}
for(const href of [official+'/message','https://evil.invalid/user/profile/'+profileId]){
throws(()=>assertNativeUiResultLocation(context,href),'SOURCE_CHANGED');
}
for(const kind of ['read','extract','wait','scroll','fill']){
const context={executionProfile:NAVIGATION_UI_PROFILE,kind,pageUrl:profileUrl};
eq(assertNativeUiResultLocation(context,profileUrl),true);
throws(()=>assertNativeUiResultLocation(context,profileUrl+'&changed=1'),'SOURCE_CHANGED');
}
});
await group('navigation diagnostics expose only the fixed comparison schema and no original identifiers or values',()=>{
const keys=['expected_kind','actual_kind','same_origin','same_pathname','same_object_id','same_query','same_query_pairs','same_hash','xsec_token','xsec_source','other_query_equal'].sort();
const boolKeys=['same_origin','same_pathname','same_object_id','same_query','same_query_pairs','same_hash','other_query_equal'];
const kinds=new Set(['note','profile','notification','explore','search','invalid']);
const validate=value=>{
eq(Object.keys(value).sort(),keys);
ok(kinds.has(value.expected_kind));ok(kinds.has(value.actual_kind));
for(const key of boolKeys) eq(typeof value[key],'boolean',key);
for(const key of ['xsec_token','xsec_source']){
eq(Object.keys(value[key]).sort(),['actual_count','expected_count','values_equal']);
for(const name of ['actual_count','expected_count']) ok(Number.isInteger(value[key][name]) && value[key][name]>=0 && value[key][name]<=1000);
eq(typeof value[key].values_equal,'boolean');
}
};
const token='LOCAL_SENSITIVE_TOKEN_8f31',source='LOCAL_TRACKING_SOURCE_c82f',unknown='LOCAL_UNKNOWN_QUERY_SECRET_4a17';
const left=`${official}/user/profile/${profileId}?xsec_token=${token}&xsec_source=${source}&private_fixture_key=${unknown}#LOCAL_HASH_MARKER`;
const right=`${official}/user/profile/${otherId}?xsec_token=DIFFERENT_LOCAL_TOKEN&xsec_source=${source}&private_fixture_key=${unknown}#OTHER_LOCAL_HASH`;
const comparison=navigationComparison(left,right);validate(comparison);
eq(comparison.expected_kind,'profile');eq(comparison.actual_kind,'profile');
eq(comparison.same_origin,true);eq(comparison.same_pathname,false);eq(comparison.same_object_id,false);
eq(comparison.same_query,false);eq(comparison.same_query_pairs,false);eq(comparison.same_hash,false);
eq(comparison.xsec_token,{expected_count:1,actual_count:1,values_equal:false});
eq(comparison.xsec_source,{expected_count:1,actual_count:1,values_equal:true});eq(comparison.other_query_equal,true);
const serialized=JSON.stringify(comparison);
for(const marker of [official,profileId,otherId,token,source,unknown,'private_fixture_key','LOCAL_HASH_MARKER','OTHER_LOCAL_HASH','DIFFERENT_LOCAL_TOKEN']){
eq(serialized.includes(marker),false,'fixed diagnostics cannot disclose '+marker);
}
const invalid=navigationComparison(null,'not a url');validate(invalid);
eq(invalid.expected_kind,'invalid');eq(invalid.actual_kind,'invalid');
for(const key of boolKeys) eq(invalid[key],false);
const many=Array.from({length:1001},()=>['xsec_token','LOCAL_COUNT_ONLY']);
const repeated=profileUrl+'&'+new URLSearchParams(many).toString();
const bounded=navigationComparison(repeated,repeated);validate(bounded);
eq(bounded.xsec_token,{expected_count:1000,actual_count:1000,values_equal:true});
});
await group('encoding tracking order and unknown query differences stay diagnostic and never authorize new navigation',()=>{
const base=`${official}/user/profile/${profileId}`;
const encoded=navigationComparison(base+'?q=local+fixture&xsec_token=%2F',base+'?q=local%20fixture&xsec_token=/');
eq(encoded.same_query,false);eq(encoded.same_query_pairs,true);eq(encoded.xsec_token.values_equal,true);eq(encoded.other_query_equal,true);
const tracking=navigationComparison(base+'?q=fixture&xsec_token=LEFT&xsec_source=one',base+'?q=fixture&xsec_token=RIGHT&xsec_source=two');
eq(tracking.same_object_id,true);eq(tracking.same_query,false);eq(tracking.other_query_equal,true);
eq(tracking.xsec_token.values_equal,false);eq(tracking.xsec_source.values_equal,false);
const duplicate=navigationComparison(base+'?xsec_token=one&xsec_token=two',base+'?xsec_token=one');
eq(duplicate.xsec_token,{expected_count:2,actual_count:1,values_equal:false});
const order=navigationComparison(base+'?a=one&b=two',base+'?b=two&a=one');
eq(order.same_query_pairs,false);eq(order.other_query_equal,false);
const unknown=navigationComparison(base+'?unknown=one',base+'?unknown=two');
eq(unknown.other_query_equal,false);eq(unknown.xsec_token.values_equal,true);eq(unknown.xsec_source.values_equal,true);
const hash=navigationComparison(base+'?q=fixture#one',base+'?q=fixture#two');
eq(hash.same_query,true);eq(hash.same_query_pairs,true);eq(hash.same_hash,false);
for(const [left,right] of [[base+'?q=local+fixture',base+'?q=local%20fixture'],
[base+'?xsec_token=one',base+'?xsec_token=two'],[base+'?unknown=one',base+'?unknown=two'],
[base+'?a=one&b=two',base+'?b=two&a=one'],[base+'#one',base+'#two']]){
const context=action(left,'profile');let error;
try{assertNativeUiResultLocation(context,right);}catch(value){error=value;}
ok(error instanceof ActionInputError);eq(error.code,'NAVIGATION_BINDING_MISMATCH');
eq(error.navigation_comparison,navigationComparison(left,right));
eq(navigationPopupMatches(left,right),false,'diagnostic equality facts never weaken full profile URL binding');
}
});
await group('only new navigation mismatch errors receive comparison fields and old error shapes stay unchanged',()=>{
const expected=`${official}/user/profile/${profileId}?xsec_token=LOCAL_ERROR_SECRET&unknown=LOCAL_UNKNOWN_SECRET`;
const context=action(expected,'profile');let mismatch;
try{assertNativeUiResultLocation(context,expected+'&changed=1');}catch(error){mismatch=error;}
ok(mismatch instanceof ActionInputError);eq(mismatch.code,'NAVIGATION_BINDING_MISMATCH');
eq(mismatch.navigation_comparison.same_object_id,true);eq(mismatch.navigation_comparison.other_query_equal,false);
for(const marker of ['LOCAL_ERROR_SECRET','LOCAL_UNKNOWN_SECRET',profileId,official,'unknown']){
eq((JSON.stringify(mismatch)+' '+mismatch.message).includes(marker),false);
}
const cases=[
[{executionProfile:NATIVE_UI_PROFILE,kind:'click',pageUrl:official+'/explore',href:note},`${official}/explore/${otherId}`,'NOTE_BINDING_MISMATCH'],
[{kind:'click',pageUrl:official+'/explore',href:note},`${official}/explore/${otherId}`,'NOTE_BINDING_MISMATCH'],
[context,'https://evil.invalid/user/profile/'+profileId,'SOURCE_CHANGED'],
[{executionProfile:NAVIGATION_UI_PROFILE,kind:'read',pageUrl:profileUrl},profileUrl+'&changed=1','SOURCE_CHANGED'],
];
for(const [current,href,code] of cases){
let error;try{assertNativeUiResultLocation(current,href);}catch(value){error=value;}
ok(error instanceof ActionInputError);eq(error.code,code);eq(Object.hasOwn(error,'navigation_comparison'),false);
}
});
await group('popup continuation binds complete non-note destinations and the fixed note identity',()=>{
for(const href of [profileUrl,official+'/notification',official+'/explore',official+'/search_result?keyword=fixture']){
eq(navigationPopupMatches(href,href),true);
for(const changed of [href+'#changed',href+(href.includes('?') ? '&changed=1' : '?changed=1')]){
eq(navigationPopupMatches(href,changed),false);
eq(navigationPopupMatches(changed,href),false);
}
}
for(const expected of [undefined,null,'',{},'about:blank',official+'/message','https://evil.invalid/explore']){
eq(navigationPopupMatches(expected,profileUrl),false);
eq(navigationPopupMatches(profileUrl,expected),false);
}
for(const actualUrl of [`${official}/user/profile/${otherId}?source=LOCAL_FIXTURE`,
`${official}/user/profile/${profileId}`,note,official+'/notification',
`https://u:p@www.xiaohongshu.com/user/profile/${profileId}?source=LOCAL_FIXTURE`,
`https://www.xiaohongshu.com:8443/user/profile/${profileId}?source=LOCAL_FIXTURE`,
`https://www.xiaohongshu.com.evil.invalid/user/profile/${profileId}?source=LOCAL_FIXTURE`,
`http://www.xiaohongshu.com/user/profile/${profileId}?source=LOCAL_FIXTURE`,
official+'/api/sns/web/v1/follow']) eq(navigationPopupMatches(profileUrl,actualUrl),false);
});
const {beginNativeUiRequestPolicy,evaluatePlatformRequest,matchPlatformRequest,endPlatformRequestPolicy}=await import('./platform-policy.mjs');
const policies=new Set();
const mint=context=>{const policy=beginNativeUiRequestPolicy(context);ok(policy);policies.add(policy);return policy;};
const request={automating:true,sessionOrigin:official,pageUrl:note,activeMainFrame:true,method:'POST',resourceType:'xhr',
url:'https://so.xiaohongshu.com/api/synthetic-native-request',now:10001};
const allow={allowed:true,policy_version:'xhs-page-navigation-v1',policy_rule:'native_page_traffic'};
const denied=(context,reason)=>{
const value=evaluatePlatformRequest(context);eq(value.allowed,false);if(reason) eq(value.reason,reason);eq(matchPlatformRequest(context),null);
};
await group('navigation request capabilities bind the original source and complete target',()=>{
const context={...action(profileUrl,'profile'),sessionOrigin:official,now:10000};
const policy=mint(context);
eq(policy,{version:'xhs-page-navigation-v1',scope:'native_ui',expiresAt:25000});eq(Object.isFrozen(policy),true);
eq(evaluatePlatformRequest({...request,policy}),allow);
eq(evaluatePlatformRequest({...request,policy,pageUrl:profileUrl}),allow);
for(const pageUrl of [profileUrl+'&changed=1',profileUrl+'#changed',`${official}/user/profile/${otherId}`,
official+'/notification',note+'&changed=1',official+'/explore']) denied({...request,policy,pageUrl},'page_changed');
context.pageUrl=official+'/explore';context.href=official+'/notification';context.actualRoots[0].href=context.href;
eq(evaluatePlatformRequest({...request,policy,pageUrl:profileUrl}),allow,'issued source facts cannot be mutated by the caller');
for(const href of [official+'/explore',official+'/notification',official+'/search_result?keyword=fixture']){
const kind=navigationTarget(href).kind;
const policy=mint({...action(href,kind),sessionOrigin:official,now:10000});
eq(evaluatePlatformRequest({...request,policy,pageUrl:href}),allow);
denied({...request,policy,pageUrl:profileUrl},'page_changed');
}
});
await group('note results allow only the same fixed identity across standard aliases and tracking changes',()=>{
const context=action(note,'note',official+'/search_result?keyword=fixture');
const policy=mint({...context,sessionOrigin:official,now:10000});
const aliases=[note,`${official}/search_result/${noteId}`,`${official}/explore/${noteId}`,
`${official}/explore/${noteId}?xsec_token=OTHER_LOCAL_TRACKING&source=fixture`,
`${official}/search_result/${noteId.toUpperCase()}?source=changed#fixture`];
for(const pageUrl of aliases){
eq(assertNativeUiResultLocation(context,pageUrl),true);
eq(evaluatePlatformRequest({...request,policy,pageUrl}),allow);
eq(navigationPopupMatches(note,pageUrl),true);
eq(navigationPopupMatches(pageUrl,note),true);
}
// Aliases are accepted only after a pinned actual anchor was activated.
// The click's precondition still binds the exact href observed on that node.
reject({...context,actualRoots:[actual(`${official}/search_result/${noteId}`,'note')]});
reject({...context,href:`${official}/search_result/${noteId}`});
for(const pageUrl of [`${official}/explore/${otherId}`,`${official}/search_result/${otherId}`,
`${official}/user/profile/${noteId}`,official+'/explore',official+'/notification',
`${official}/explore/${noteId}/delete`,`${official}/search_result_ai/${noteId}`,
`https://evil.invalid/explore/${noteId}`,`https://www.xiaohongshu.com:8443/explore/${noteId}`,
`https://u:p@www.xiaohongshu.com/explore/${noteId}`]){
const inScope=validNavigationPage(pageUrl);
throws(()=>assertNativeUiResultLocation(context,pageUrl),inScope ? 'NAVIGATION_BINDING_MISMATCH' : 'SOURCE_CHANGED');
denied({...request,policy,pageUrl});
eq(navigationPopupMatches(note,pageUrl),false);
}
return {note_equivalence:'official standard note route and identical note ID only; non-note target hrefs stay exact',
pre_click_href_exact:true};
});
await group('profile hydration may only omit the two observed single channel context parameters',async()=>{
const base=`${official}/user/profile/${profileId}`;
const fixed='xsec_token=LOCAL_FIXED_TOKEN&xsec_source=LOCAL_FIXED_SOURCE';
const expected=base+'?'+fixed+'&channel_type=LOCAL_CHANNEL&parent_page_channel_type=LOCAL_PARENT&other=LOCAL_OTHER#fixed';
const context=action(expected,'profile',note);
const policy=mint({...context,sessionOrigin:official,now:10000});
const allowedUrls=[expected,
base+'?'+fixed+'&parent_page_channel_type=LOCAL_PARENT&other=LOCAL_OTHER#fixed',
base+'?'+fixed+'&channel_type=LOCAL_CHANNEL&other=LOCAL_OTHER#fixed',
base+'?'+fixed+'&other=LOCAL_OTHER#fixed'];
for(const pageUrl of allowedUrls){
eq(assertNativeUiResultLocation(context,pageUrl),true);
eq(evaluatePlatformRequest({...request,policy,pageUrl}),allow);
eq(navigationPopupMatches(expected,pageUrl),true);
}
// An existing empty value is still a single observed pair, and can only
// disappear; no arbitrary new channel pair may be created during hydration.
eq(navigationPopupMatches(base+'?channel_type=',base),true);
eq(navigationPopupMatches(base+'?parent_page_channel_type=',base),true);
const changedUrls=[
base+'?'+fixed+'&channel_type=CHANGED&parent_page_channel_type=LOCAL_PARENT&other=LOCAL_OTHER#fixed',
base+'?'+fixed+'&parent_page_channel_type=CHANGED&other=LOCAL_OTHER#fixed',
base+'?'+fixed+'&channel_type=LOCAL_CHANNEL&channel_type=LOCAL_CHANNEL&other=LOCAL_OTHER#fixed',
base+'?'+fixed+'&parent_page_channel_type=LOCAL_PARENT&parent_page_channel_type=LOCAL_PARENT&other=LOCAL_OTHER#fixed',
base+'?xsec_token=CHANGED&xsec_source=LOCAL_FIXED_SOURCE&other=LOCAL_OTHER#fixed',
base+'?xsec_token=LOCAL_FIXED_TOKEN&xsec_source=CHANGED&other=LOCAL_OTHER#fixed',
base+'?xsec_source=LOCAL_FIXED_SOURCE&other=LOCAL_OTHER#fixed',
base+'?'+fixed+'&other=CHANGED#fixed',base+'?'+fixed+'#fixed',
base+'?'+fixed+'&other=LOCAL_OTHER&added=NEW#fixed',
base+'?other=LOCAL_OTHER&'+fixed+'#fixed',
base+'?'+fixed+'&other=LOCAL_OTHER#changed',base+'?'+fixed+'&other=LOCAL_OTHER',
`${official}/user/profile/${otherId}?${fixed}&other=LOCAL_OTHER#fixed`,
`${official}/explore/${profileId}?${fixed}&other=LOCAL_OTHER#fixed`,
`https://evil.invalid/user/profile/${profileId}?${fixed}&other=LOCAL_OTHER#fixed`,
`https://www.xiaohongshu.com:8443/user/profile/${profileId}?${fixed}&other=LOCAL_OTHER#fixed`,
`https://u:p@www.xiaohongshu.com/user/profile/${profileId}?${fixed}&other=LOCAL_OTHER#fixed`,
];
for(const pageUrl of changedUrls){
eq(navigationPopupMatches(expected,pageUrl),false);
throws(()=>assertNativeUiResultLocation(context,pageUrl),validNavigationPage(pageUrl) ? 'NAVIGATION_BINDING_MISMATCH' : 'SOURCE_CHANGED');
denied({...request,policy,pageUrl});
}
for(const [left,right] of [
[base+'?'+fixed,base+'?'+fixed+'&channel_type=NEW'],
[base+'?'+fixed,base+'?'+fixed+'&parent_page_channel_type=NEW'],
[base+'?channel_type=one',base+'?parent_page_channel_type=NEW'],
[base+'?parent_page_channel_type=one',base+'?channel_type=NEW'],
[base+'?channel_type=one&channel_type=two',base],
[base+'?parent_page_channel_type=one&parent_page_channel_type=two',base],
[base+'?channel_type=one&channel_type=one',base+'?channel_type=one'],
]) eq(navigationPopupMatches(left,right),false);
const duplicateExact=base+'?channel_type=one&channel_type=one';
eq(navigationPopupMatches(duplicateExact,duplicateExact),true,'an unchanged complete href keeps the existing exact-match contract');
// The extra rule applies to profiles only. Notification and search targets
// must not inherit profile-specific channel omission behavior.
for(const pathname of ['/explore','/notification','/search_result']){
eq(navigationPopupMatches(official+pathname+'?channel_type=one',official+pathname),false);
}
const {nativePopupRequestContext}=await import('./browser-worker.mjs');
const sourceFrame={},popupFrame={};
const source={url:()=>note,mainFrame:()=>sourceFrame},popup={url:()=>allowedUrls[3],mainFrame:()=>popupFrame};
sourceFrame.page=()=>source;popupFrame.page=()=>popup;
const permission={opener:source,sourceUrl:note,expectedUrl:expected,pendingPopup:popup,adopted:false};
const popupContext={executionProfile:NAVIGATION_UI_PROFILE,automating:true,policy,policyPage:source,currentPage:source,
permission,requestPage:popup,requestFrame:popupFrame,popupOpener:source,now:10001};
eq(nativePopupRequestContext(popupContext),{activeMainFrame:true,pageUrl:allowedUrls[3]});
eq(nativePopupRequestContext({...popupContext,popupOpener:null}),null);
eq(nativePopupRequestContext({...popupContext,now:25000}),null);
eq(nativePopupRequestContext({...popupContext,executionProfile:NATIVE_UI_PROFILE}),null);
eq(policy.expiresAt,25000);
reject({...context,actualRoots:[actual(allowedUrls[3],'profile')]},'WRITE_ACTION_BLOCKED');
return {fixed_omittable_keys:['channel_type','parent_page_channel_type'],other_query_values_and_order_preserved:true,
popup_expiry_and_opener_unchanged:true,pre_click_href_exact:true};
});
await group('fifteen second capabilities reject early exact expiry revoked and forged use',()=>{
const policy=mint({...action(profileUrl,'profile'),sessionOrigin:official,now:10000});
eq(evaluatePlatformRequest({...request,policy,now:10000}),allow);
eq(evaluatePlatformRequest({...request,policy,now:24999}),allow);
denied({...request,policy,now:9999},'policy_not_started');
for(const now of [25000,25001]) denied({...request,policy,now},'policy_expired');
for(const now of [NaN,Infinity,null,'10001']) denied({...request,policy,now},'invalid_time');
denied({...request,policy:{...policy}},'inactive_policy');
endPlatformRequestPolicy(policy);denied({...request,policy},'inactive_policy');
});
await group('request authority remains first party HTTPS xhr fetch and requires a permitted UI action',()=>{
const context={...action(profileUrl,'profile'),sessionOrigin:official,now:10000};
const policy=mint(context);
for(const host of ['www.xiaohongshu.com','xiaohongshu.com','so.xiaohongshu.com','as.xiaohongshu.com']){
for(const resourceType of ['xhr','fetch']) eq(evaluatePlatformRequest({...request,policy,resourceType,url:`https://${host}/synthetic-native`}),allow);
}
for(const patch of [{activeMainFrame:false},{automating:false},{sessionOrigin:'https://evil.invalid'},
{pageUrl:official+'/message'},{pageUrl:'https://evil.invalid/explore'},
{url:'https://www.xiaohongshu.com.evil.invalid/synthetic-native'},
{url:'https://u:p@so.xiaohongshu.com/synthetic-native'},
{url:'https://so.xiaohongshu.com:8443/synthetic-native'},
{url:'http://so.xiaohongshu.com/synthetic-native'},
{url:'http://127.0.0.1/synthetic-native'},
{resourceType:'document'},{resourceType:'websocket'},{method:'GET'},{method:'DELETE'}]) denied({...request,policy,...patch});
for(const patch of [{executionProfile:'unknown'},{executionProfile:null},{sessionOrigin:'https://evil.invalid'},
{kind:'follow'},{kind:'send_message'},{observed:null},{href:official+'/message'},
{actualRoots:[actual(official+'/message','notification')]},{now:NaN}]){
eq(beginNativeUiRequestPolicy({...context,...patch}),null);
}
});
await group('old v1 cannot receive the navigation page scope via copied or missing profile metadata',()=>{
const readRoot={tag:'section',readonly_root:true,sensitive:false,forbidden:false,native_context:{read_root:true}};
for(const pageUrl of [profileUrl,official+'/notification']){
for(const executionProfile of [undefined,NATIVE_UI_PROFILE]){
reject({executionProfile,kind:'read',pageUrl,actualRoots:[readRoot]},'SCOPE_BLOCKED');
eq(beginNativeUiRequestPolicy({executionProfile,kind:'read',pageUrl,actualRoots:[readRoot],sessionOrigin:official,now:10000}),null);
}
eq(assertNativeUiAction({executionProfile:NAVIGATION_UI_PROFILE,kind:'read',pageUrl,actualRoots:[readRoot]}),true);
}
});
await group('popup request handoff needs one fixed opener target and genuine main frame without renewing policy',async()=>{
const {nativePopupRequestContext}=await import('./browser-worker.mjs');
const sourceFrame={},popupFrame={},otherFrame={};
const source={url:()=>note,mainFrame:()=>sourceFrame};
const popup={url:()=>profileUrl,mainFrame:()=>popupFrame};
sourceFrame.page=()=>source;popupFrame.page=()=>popup;
const otherPopup={url:()=>profileUrl,mainFrame:()=>({})};
const policy=mint({...action(profileUrl,'profile'),sessionOrigin:official,now:10000});
const permission={opener:source,sourceUrl:note,expectedUrl:profileUrl,adopted:false,pendingPopup:popup};
const context={executionProfile:NAVIGATION_UI_PROFILE,automating:true,policy,policyPage:source,currentPage:source,
permission,requestPage:popup,requestFrame:popupFrame,popupOpener:source,now:10001};
const accepted={activeMainFrame:true,pageUrl:profileUrl};
const before=JSON.stringify({permission:{sourceUrl:permission.sourceUrl,expectedUrl:permission.expectedUrl,adopted:permission.adopted},policy});
eq(nativePopupRequestContext(context),accepted);
eq(evaluatePlatformRequest({...request,policy,...nativePopupRequestContext(context)}),allow);
eq(JSON.stringify({permission:{sourceUrl:permission.sourceUrl,expectedUrl:permission.expectedUrl,adopted:permission.adopted},policy}),before,'request qualification changes neither facts nor policy');
eq(policy.expiresAt,25000);
const adopted={...context,policyPage:popup,currentPage:popup,permission:{...permission,adopted:true}};
eq(nativePopupRequestContext(adopted),accepted);
eq(adopted.policy,policy,'adoption reuses the original issued capability');
eq(adopted.policy.expiresAt,25000,'adoption cannot start a new fifteen second window');
for(const patch of [
{executionProfile:NATIVE_UI_PROFILE},{executionProfile:undefined},{executionProfile:'unknown'},
{automating:false},{policy:null},{policy:{...policy,scope:'search'}},
{policy:{...policy,version:'xhs-page-native-v1'}},{policy:{...policy,expiresAt:10000}},
{requestPage:source},{requestFrame:sourceFrame},{requestFrame:otherFrame},{requestFrame:null},
{popupOpener:null},{popupOpener:otherPopup},{permission:null},
{permission:{...permission,manual:true}},{permission:{...permission,opener:otherPopup}},
{permission:{...permission,sourceUrl:note+'&changed=1'}},
{permission:{...permission,expectedUrl:profileUrl+'&changed=1'}},
{permission:{...permission,expectedUrl:official+'/message'}},
{permission:{...permission,pendingPopup:otherPopup}},
{permission:{...permission,adopted:true}},
{policyPage:otherPopup},{currentPage:otherPopup},
{requestPage:{url:()=>official+'/message',mainFrame:()=>popupFrame}},
{requestPage:{url:()=>profileUrl+'&changed=1',mainFrame:()=>popupFrame}},
{requestPage:{url:()=>`https://evil.invalid/user/profile/${profileId}`,mainFrame:()=>popupFrame}},
{now:25000},{now:25001},{now:NaN},{now:Infinity},{now:'10001'},
]) eq(nativePopupRequestContext({...context,...patch}),null);
for(const patch of [{policyPage:source},{currentPage:source},{permission:{...permission,adopted:true,pendingPopup:otherPopup}}]){
eq(nativePopupRequestContext({...adopted,...patch}),null);
}
// Even apparently correct public fields cannot fabricate the private issued
// policy. Qualification is followed by the existing capability evaluator.
const clone={...policy};
denied({...request,policy:clone,...(nativePopupRequestContext({...context,policy:clone}) || {})},'inactive_policy');
eq(nativePopupRequestContext({...context,now:24999}),accepted);
denied({...request,policy,now:25000,...accepted},'policy_expired');
endPlatformRequestPolicy(policy);
denied({...request,policy,...accepted},'inactive_policy');
return {pre_adoption_and_post_adoption:true,child_frames_and_unrequested_popups_rejected:true,
original_expiry:25000,reissued_policy:false,production_network:false};
});
await group('real loopback Edge popup frame and opener objects exercise the handoff predicate',async()=>{
const {nativePopupRequestContext}=await import('./browser-worker.mjs');
const {chromium}=await import('playwright');
let browser,fixtureRequests=0,unexpectedNetwork=0;
const server=createServer((req,res)=>{
fixtureRequests+=1;
const html=req.url==='/source' ? '<a id="open" rel="opener" target="_blank" href="/destination">Open local profile fixture</a>' :
req.url==='/destination' ? '<h1>Local popup</h1><iframe src="/child"></iframe><script>fetch("/local-post",{method:"POST"}).catch(()=>{})</script>' : '<p>Local child frame</p>';
res.writeHead(200,{'content-type':'text/html; charset=utf-8'}).end('<!doctype html>'+html);
});
await new Promise(resolve=>server.listen(0,'127.0.0.1',resolve));
const loopback=`http://127.0.0.1:${server.address().port}`;
try{
browser=await chromium.launch({channel:process.platform==='win32' ? 'msedge' : 'chromium',headless:true});
const browserContext=await browser.newContext({viewport:{width:1280,height:840},serviceWorkers:'block'});
let postRequest;
browserContext.on('request',req=>{if(req.method()==='POST' && req.url()===loopback+'/local-post') postRequest=req;});
await browserContext.route('**/*',async route=>{
if(new URL(route.request().url()).origin!==loopback){unexpectedNetwork+=1;await route.abort();return;}await route.continue();
});
const actualSource=await browserContext.newPage();await actualSource.goto(loopback+'/source');
const [actualPopup]=await Promise.all([actualSource.waitForEvent('popup'),actualSource.locator('#open').click()]);
await actualPopup.waitForLoadState('load');
eq(await actualPopup.opener(),actualSource,'the popup has a real explicit opener');
ok(postRequest,'local popup emitted an actual main-frame POST');
eq(postRequest.frame(),actualPopup.mainFrame());eq(postRequest.frame().page(),actualPopup);
const child=actualPopup.frames().find(frame=>frame!==actualPopup.mainFrame());ok(child);eq(child.parentFrame(),actualPopup.mainFrame());
// Only the fixed URL reads are synthetic. Frame and opener identity were
// obtained from the actual loopback browser above; no official page is
// navigated, intercepted, fetched, or accepted via an origin override.
const source=actualSource,popup=actualPopup;
const realSourceUrl=source.url.bind(source),realPopupUrl=popup.url.bind(popup);
// Project only the two URL accessors after local navigation completed;
// retain genuine Page, Frame, frame.page() and opener object identities.
source.url=()=>note;popup.url=()=>profileUrl;
const policy=mint({...action(profileUrl,'profile'),sessionOrigin:official,now:10000});
const permission={opener:source,sourceUrl:note,expectedUrl:profileUrl,pendingPopup:popup,adopted:false};
const context={executionProfile:NAVIGATION_UI_PROFILE,automating:true,policy,policyPage:source,currentPage:source,
permission,requestPage:popup,requestFrame:postRequest.frame(),popupOpener:source,now:10001};
const accepted={activeMainFrame:true,pageUrl:profileUrl};
try{
eq(nativePopupRequestContext(context),accepted);
eq(evaluatePlatformRequest({...request,policy,...accepted}),allow);
eq(nativePopupRequestContext({...context,requestFrame:child}),null);
eq(nativePopupRequestContext({...context,popupOpener:null}),null);
eq(nativePopupRequestContext({...context,permission:null}),null);
eq(nativePopupRequestContext({...context,executionProfile:NATIVE_UI_PROFILE}),null);
const adopted={...context,currentPage:popup,policyPage:popup,permission:{...permission,adopted:true}};
eq(nativePopupRequestContext(adopted),accepted);eq(adopted.policy,policy);eq(policy.expiresAt,25000);
eq(nativePopupRequestContext({...adopted,now:25000}),null);
}finally{source.url=realSourceUrl;popup.url=realPopupUrl;}
eq(nativePopupRequestContext(context),null,'unprojected real loopback URL cannot become an official profile');
eq(unexpectedNetwork,0);endPlatformRequestPolicy(policy);
return {browser:process.platform==='win32' ? 'Edge' : 'Chromium',loopback_requests:fixtureRequests,local_main_frame_post:true,
real_opener_and_frame_identity:true,official_urls_synthetic_metadata_only:true,unexpected_network_attempts:unexpectedNetwork,
production_origin_override:false,worker_navigation_profile_end_to_end:false};
}finally{if(browser) await browser.close();await new Promise(resolve=>server.close(resolve));}
});
await group('fixed loopback DOM distinguishes ordinary navigation from editable and write controls',async()=>{
const {inspectElements}=await import('./browser-worker.mjs');
const {chromium}=await import('playwright');
const observations=[];
let browser,fixtureRequests=0,unexpectedNetwork=0;
const server=createServer((req,res)=>{fixtureRequests+=1;res.writeHead(200,{'content-type':'text/html; charset=utf-8'}).end('<!doctype html><title>Local navigation DOM fixture</title>');});
await new Promise(resolve=>server.listen(0,'127.0.0.1',resolve));
const loopback=`http://127.0.0.1:${server.address().port}`;
try{
browser=await chromium.launch({channel:process.platform==='win32' ? 'msedge' : 'chromium',headless:true});
const browserContext=await browser.newContext({viewport:{width:1280,height:840},serviceWorkers:'block'});
await browserContext.route('**/*',async route=>{
if(new URL(route.request().url()).origin!==loopback){unexpectedNetwork+=1;await route.abort();return;}await route.continue();
});
const page=await browserContext.newPage();await page.goto(loopback+'/',{waitUntil:'domcontentloaded'});
const fixtures=[
['ordinary-profile',`<a id="fixture" href="${profileUrl}">Profile</a>`,'profile',true],
['ordinary-notification',`<a id="fixture" href="${official}/notification">Notifications</a>`,'notification',true],
['return-explore',`<a id="fixture" href="${official}/explore">Explore</a>`,'explore',true],
['return-search',`<a id="fixture" href="${official}/search_result?keyword=fixture">Search</a>`,'search',true],
['self-profile',`<a id="fixture" target="_self" href="${profileUrl}">Profile</a>`,'profile',true],
['download-profile',`<a id="fixture" download href="${profileUrl}">Profile</a>`,'profile',false],
['blank-profile',`<a id="fixture" target="_blank" href="${profileUrl}">Profile</a>`,'profile',true],
['blank-note',`<a id="fixture" target="_blank" href="${note}">Note</a>`,'note',true],
['blank-notification',`<a id="fixture" target="_blank" href="${official}/notification">Notifications</a>`,'notification',true],
['named-profile',`<a id="fixture" target="named" href="${profileUrl}">Profile</a>`,'profile',false],
['editable-profile',`<a id="fixture" contenteditable="true" href="${profileUrl}">Profile</a>`,'profile',false],
['inherited-editable',`<div contenteditable="plaintext-only"><a id="fixture" href="${profileUrl}">Profile</a></div>`,'profile',false],
['form-profile',`<form><a id="fixture" href="${profileUrl}">Profile</a></form>`,'profile',false],
['role-button-profile',`<a id="fixture" role="button" href="${profileUrl}">Profile</a>`,'profile',false],
['button-ancestor-profile',`<div role="button"><a id="fixture" href="${profileUrl}">Profile</a></div>`,'profile',false],
['hidden-profile',`<a id="fixture" style="display:none" href="${profileUrl}">Profile</a>`,'profile',false],
['follow-button','<button id="fixture">关注</button>','profile',false],
['message-button','<button id="fixture">发私信</button>','profile',false],
['message-input','<textarea id="fixture" aria-label="Message"></textarea>','profile',false],
['external-profile',`<a id="fixture" href="https://evil.invalid/user/profile/${profileId}">Profile</a>`,'profile',false],
['message-link',`<a id="fixture" href="${official}/message">Message</a>`,'profile',false],
];
for(const [name,html,kind,accepted] of fixtures){
await page.setContent(`<!doctype html><html><head><title>Local only</title><style>body{font:18px sans-serif;margin:24px}a,button,textarea{display:inline-block;margin:10px;padding:12px}</style></head><body><h1>Navigation DOM metadata</h1>${html}</body></html>`);
const states=await page.locator('#fixture').evaluate(inspectElements,{origin:official,nativeProfile:true,navigationProfile:true});
eq(states.length,1);const state=states[0];
const href=typeof state.href==='string' ? state.href : profileUrl;
const context={...action(href,kind),actualRoots:states};
if(accepted){eq(assertNativeUiAction(context),true);eq(state.native_context[kind==='note' ? 'note_link' : kind==='profile' ? 'profile_link' : 'navigation_link'],true);}
else reject(context);
observations.push({name,accepted,tag:state.tag,visible:state.visible,sensitive:state.sensitive,native_context:state.native_context});
}
// Metadata is profile-specific: allowing one explicitly bound popup in the
// new profile does not change the old profile's same-window-only anchors.
await page.setContent(`<a id="fixture" target="_blank" href="${note}">Note</a>`);
const blankOld=await page.locator('#fixture').evaluate(inspectElements,{origin:official,nativeProfile:true});
eq(blankOld[0].native_context.note_link,false);
reject({...action(note,'note',official+'/explore'),executionProfile:NATIVE_UI_PROFILE,actualRoots:blankOld});
const blankNew=await page.locator('#fixture').evaluate(inspectElements,{origin:official,nativeProfile:true,navigationProfile:true});
eq(blankNew[0].native_context.note_link,true);
eq(assertNativeUiAction({...action(note,'note',official+'/explore'),actualRoots:blankNew}),true);
await page.setContent(`<a id="fixture" href="${profileUrl}">Profile</a>`);
const legacy=await page.locator('#fixture').evaluate(inspectElements,{origin:official});
eq(Object.hasOwn(legacy[0],'native_context'),false);
const old=await page.locator('#fixture').evaluate(inspectElements,{origin:official,nativeProfile:true});
eq(Object.hasOwn(old[0].native_context,'profile_link'),false,'new navigation metadata is absent under old inspection');
const screenshot=path.join(output,'loopback-navigation-dom.png');await page.screenshot({path:screenshot,fullPage:true});
eq((await fs.readFile(screenshot)).subarray(1,4).toString(),'PNG');
eq(unexpectedNetwork,0,'official-looking links were never navigated or fetched');
await fs.writeFile(path.join(output,'loopback-dom-observations.json'),JSON.stringify(observations,null,2));
browserFixture={browser:process.platform==='win32' ? 'Edge' : 'Chromium',origin:'loopback only',inspected_cases:observations.length,
fixture_requests:fixtureRequests,unexpected_network_attempts:unexpectedNetwork,screenshot,actual_png:true,
worker_navigation_profile_end_to_end:false};return browserFixture;
}finally{if(browser) await browser.close();await new Promise(resolve=>server.close(resolve));}
});
for(const policy of policies) endPlatformRequestPolicy(policy);
await report();
console.log(JSON.stringify({passed:results.length,assertions,output,official_browser_navigation:false,
live_platform_requests:0,application_api_calls:0,model_calls:0}));