mirror of
https://github.com/yaklang/yaklang-chrome-extension.git
synced 2026-09-22 03:10:43 +08:00
fix(authorization): keep capture across same-origin login
This commit is contained in:
@@ -3,7 +3,9 @@ import type {
|
||||
YakPocGenerateResult,
|
||||
} from '@/types/models';
|
||||
import type { CapabilityDomainHandler } from '../capability-context';
|
||||
import { allowedTarget, requireScope } from '../capability-context';
|
||||
import {
|
||||
allowedTarget, PAIRED_BROWSER_INSTANCE_ACCESS_ID, requireScope,
|
||||
} from '../capability-context';
|
||||
import {
|
||||
clearNetworkRequests,
|
||||
exportNetworkRequest,
|
||||
@@ -30,7 +32,12 @@ export const networkCapabilityHandler: CapabilityDomainHandler = {
|
||||
captureBody: input.captureBody === true,
|
||||
maxEntries: typeof input.maxEntries === 'number' ? input.maxEntries : undefined,
|
||||
maxBodyBytes: typeof input.maxBodyBytes === 'number' ? input.maxBodyBytes : undefined,
|
||||
}, { kind: 'grant', grantId: grant.id, expiresAt: grant.expiresAt });
|
||||
}, {
|
||||
kind: 'grant',
|
||||
grantId: grant.id,
|
||||
expiresAt: grant.expiresAt,
|
||||
followSameOriginNavigation: grant.id === PAIRED_BROWSER_INSTANCE_ACCESS_ID,
|
||||
});
|
||||
}
|
||||
if (method === 'browser.network.status') return networkCaptureStatus(target);
|
||||
if (method === 'browser.network.list') {
|
||||
|
||||
@@ -185,6 +185,37 @@ describe('network capture lifecycle, budget and persistence', () => {
|
||||
expect((await networkCaptureStatus({ tabId: 43, frameId: 0, documentId: 'document-cross-origin' })).active).toBe(false);
|
||||
});
|
||||
|
||||
it('continues a paired-browser capture across a same-origin login navigation', async () => {
|
||||
setTarget(45, 'http://localhost:8080/logic/user/login', 'document-login');
|
||||
const before = await start(
|
||||
45,
|
||||
{ captureHeaders: true, captureBody: true },
|
||||
{
|
||||
kind: 'grant',
|
||||
grantId: 'paired-browser-instance',
|
||||
expiresAt: Number.MAX_SAFE_INTEGER,
|
||||
followSameOriginNavigation: true,
|
||||
},
|
||||
);
|
||||
|
||||
await committed(45, 'http://localhost:8080/logic/user/profile', 'document-profile');
|
||||
|
||||
const after = await networkCaptureStatus({ tabId: 45, frameId: 0, documentId: 'document-profile' });
|
||||
expect(after).toMatchObject({ active: true, startedAt: before.startedAt });
|
||||
|
||||
await committed(45, 'http://other.example/landing', 'document-other');
|
||||
expect((await networkCaptureStatus({ tabId: 45, frameId: 0, documentId: 'document-other' })).active).toBe(false);
|
||||
});
|
||||
|
||||
it('does not silently extend a regular scoped grant across navigation', async () => {
|
||||
setTarget(46, 'https://scoped.example.test/start', 'document-start');
|
||||
await start(46, {}, { kind: 'grant', grantId: 'scoped-grant', expiresAt: NOW + 60_000 });
|
||||
|
||||
await committed(46, 'https://scoped.example.test/next', 'document-next');
|
||||
|
||||
expect((await networkCaptureStatus({ tabId: 46, frameId: 0, documentId: 'document-next' })).active).toBe(false);
|
||||
});
|
||||
|
||||
it('does not retain a cross-origin navigation request before the commit boundary is processed', async () => {
|
||||
setTarget(44, 'https://source.example.test/start', 'document-source');
|
||||
await start(44);
|
||||
|
||||
@@ -32,7 +32,9 @@ const CAPTURED_RESOURCE_TYPES = [
|
||||
] as const;
|
||||
|
||||
type CapturePersistence = 'pending' | 'persisted' | 'memory-only' | 'degraded';
|
||||
type CaptureOwner = { kind: 'local' } | { kind: 'grant'; grantId: string; expiresAt: number };
|
||||
type CaptureOwner = { kind: 'local' } | {
|
||||
kind: 'grant'; grantId: string; expiresAt: number; followSameOriginNavigation?: boolean;
|
||||
};
|
||||
|
||||
interface CaptureSession {
|
||||
target: BrowserTarget;
|
||||
@@ -270,7 +272,7 @@ function addRestoredSession(value: PersistedCaptureSession): boolean {
|
||||
options: normalizedOptions(value.options),
|
||||
records,
|
||||
owner: value.owner?.kind === 'grant' && typeof value.owner.grantId === 'string' && typeof value.owner.expiresAt === 'number'
|
||||
? value.owner
|
||||
? { ...value.owner, followSameOriginNavigation: value.owner.followSameOriginNavigation === true }
|
||||
: { kind: 'local' },
|
||||
retainedBytes,
|
||||
recordBytes,
|
||||
@@ -283,7 +285,11 @@ function addRestoredSession(value: PersistedCaptureSession): boolean {
|
||||
totalRecordCount += records.length;
|
||||
totalRetainedBytes += retainedBytes;
|
||||
const ownerWasValid = value.owner?.kind === 'local'
|
||||
|| (value.owner?.kind === 'grant' && typeof value.owner.grantId === 'string' && typeof value.owner.expiresAt === 'number');
|
||||
|| (value.owner?.kind === 'grant'
|
||||
&& typeof value.owner.grantId === 'string'
|
||||
&& typeof value.owner.expiresAt === 'number'
|
||||
&& (value.owner.followSameOriginNavigation === undefined
|
||||
|| typeof value.owner.followSameOriginNavigation === 'boolean'));
|
||||
return records.length === value.records.length && ownerWasValid && !existing;
|
||||
}
|
||||
|
||||
@@ -605,7 +611,7 @@ browser.webRequest.onErrorOccurred.addListener((details) => {
|
||||
dispatchCaptureEvent(errorRecord, details);
|
||||
}, { urls: ['<all_urls>'], types: [...CAPTURED_RESOURCE_TYPES] });
|
||||
|
||||
async function rebindLocalCaptureAfterNavigation(details: {
|
||||
async function rebindCaptureAfterNavigation(details: {
|
||||
tabId: number;
|
||||
frameId: number;
|
||||
documentId?: string;
|
||||
@@ -613,7 +619,8 @@ async function rebindLocalCaptureAfterNavigation(details: {
|
||||
}): Promise<void> {
|
||||
await restorePromise;
|
||||
const session = captureSessions.get(details.tabId);
|
||||
if (!session || session.owner.kind !== 'local' || session.target.frameId !== details.frameId) return;
|
||||
if (!session || session.target.frameId !== details.frameId
|
||||
|| (session.owner.kind === 'grant' && !session.owner.followSameOriginNavigation)) return;
|
||||
if (details.documentId && session.target.documentId === details.documentId) return;
|
||||
let isolationBoundary: string | undefined;
|
||||
try {
|
||||
@@ -639,7 +646,7 @@ async function rebindLocalCaptureAfterNavigation(details: {
|
||||
}
|
||||
|
||||
browser.webNavigation.onCommitted.addListener((details) => {
|
||||
void rebindLocalCaptureAfterNavigation(details).catch(() => undefined);
|
||||
void rebindCaptureAfterNavigation(details).catch(() => undefined);
|
||||
});
|
||||
browser.tabs.onRemoved.addListener((tabId) => {
|
||||
if (!deleteSession(tabId)) return;
|
||||
|
||||
Reference in New Issue
Block a user