safari-pilot 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +35 -0
- package/.mcp.json +11 -0
- package/LICENSE +21 -0
- package/README.md +324 -0
- package/bin/.gitkeep +0 -0
- package/bin/Safari Pilot.app/Contents/CodeResources +0 -0
- package/bin/Safari Pilot.app/Contents/Info.plist +58 -0
- package/bin/Safari Pilot.app/Contents/MacOS/Safari Pilot +0 -0
- package/bin/Safari Pilot.app/Contents/PkgInfo +1 -0
- package/bin/Safari Pilot.app/Contents/PlugIns/Safari Pilot Extension.appex/Contents/Info.plist +55 -0
- package/bin/Safari Pilot.app/Contents/PlugIns/Safari Pilot Extension.appex/Contents/MacOS/Safari Pilot Extension +0 -0
- package/bin/Safari Pilot.app/Contents/PlugIns/Safari Pilot Extension.appex/Contents/Resources/background.js +294 -0
- package/bin/Safari Pilot.app/Contents/PlugIns/Safari Pilot Extension.appex/Contents/Resources/content-isolated.js +80 -0
- package/bin/Safari Pilot.app/Contents/PlugIns/Safari Pilot Extension.appex/Contents/Resources/content-main.js +310 -0
- package/bin/Safari Pilot.app/Contents/PlugIns/Safari Pilot Extension.appex/Contents/Resources/icons/icon-128.png +0 -0
- package/bin/Safari Pilot.app/Contents/PlugIns/Safari Pilot Extension.appex/Contents/Resources/icons/icon-48.png +0 -0
- package/bin/Safari Pilot.app/Contents/PlugIns/Safari Pilot Extension.appex/Contents/Resources/icons/icon-96.png +0 -0
- package/bin/Safari Pilot.app/Contents/PlugIns/Safari Pilot Extension.appex/Contents/Resources/manifest.json +39 -0
- package/bin/Safari Pilot.app/Contents/PlugIns/Safari Pilot Extension.appex/Contents/_CodeSignature/CodeResources +194 -0
- package/bin/Safari Pilot.app/Contents/Resources/AppIcon.icns +0 -0
- package/bin/Safari Pilot.app/Contents/Resources/Assets.car +0 -0
- package/bin/Safari Pilot.app/Contents/Resources/Base.lproj/Main.html +19 -0
- package/bin/Safari Pilot.app/Contents/Resources/Base.lproj/Main.storyboardc/Info.plist +0 -0
- package/bin/Safari Pilot.app/Contents/Resources/Base.lproj/Main.storyboardc/MainMenu.nib +0 -0
- package/bin/Safari Pilot.app/Contents/Resources/Base.lproj/Main.storyboardc/NSWindowController-B8D-0N-5wS.nib +0 -0
- package/bin/Safari Pilot.app/Contents/Resources/Base.lproj/Main.storyboardc/XfG-lQ-9wD-view-m2S-Jp-Qdl.nib +0 -0
- package/bin/Safari Pilot.app/Contents/Resources/Icon.png +0 -0
- package/bin/Safari Pilot.app/Contents/Resources/Script.js +22 -0
- package/bin/Safari Pilot.app/Contents/Resources/Style.css +45 -0
- package/bin/Safari Pilot.app/Contents/_CodeSignature/CodeResources +236 -0
- package/bin/Safari Pilot.zip +0 -0
- package/bin/SafariPilotd +0 -0
- package/dist/engine-selector.d.ts +10 -0
- package/dist/engine-selector.js +55 -0
- package/dist/engine-selector.js.map +1 -0
- package/dist/engines/applescript.d.ts +53 -0
- package/dist/engines/applescript.js +290 -0
- package/dist/engines/applescript.js.map +1 -0
- package/dist/engines/daemon.d.ts +19 -0
- package/dist/engines/daemon.js +187 -0
- package/dist/engines/daemon.js.map +1 -0
- package/dist/engines/engine.d.ts +15 -0
- package/dist/engines/engine.js +42 -0
- package/dist/engines/engine.js.map +1 -0
- package/dist/engines/extension.d.ts +34 -0
- package/dist/engines/extension.js +66 -0
- package/dist/engines/extension.js.map +1 -0
- package/dist/errors.d.ts +128 -0
- package/dist/errors.js +250 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/security/audit-log.d.ts +23 -0
- package/dist/security/audit-log.js +68 -0
- package/dist/security/audit-log.js.map +1 -0
- package/dist/security/circuit-breaker.d.ts +29 -0
- package/dist/security/circuit-breaker.js +114 -0
- package/dist/security/circuit-breaker.js.map +1 -0
- package/dist/security/domain-policy.d.ts +29 -0
- package/dist/security/domain-policy.js +96 -0
- package/dist/security/domain-policy.js.map +1 -0
- package/dist/security/human-approval.d.ts +20 -0
- package/dist/security/human-approval.js +150 -0
- package/dist/security/human-approval.js.map +1 -0
- package/dist/security/idpi-scanner.d.ts +20 -0
- package/dist/security/idpi-scanner.js +102 -0
- package/dist/security/idpi-scanner.js.map +1 -0
- package/dist/security/kill-switch.d.ts +51 -0
- package/dist/security/kill-switch.js +103 -0
- package/dist/security/kill-switch.js.map +1 -0
- package/dist/security/rate-limiter.d.ts +30 -0
- package/dist/security/rate-limiter.js +70 -0
- package/dist/security/rate-limiter.js.map +1 -0
- package/dist/security/screenshot-redaction.d.ts +42 -0
- package/dist/security/screenshot-redaction.js +134 -0
- package/dist/security/screenshot-redaction.js.map +1 -0
- package/dist/security/tab-ownership.d.ts +46 -0
- package/dist/security/tab-ownership.js +85 -0
- package/dist/security/tab-ownership.js.map +1 -0
- package/dist/server.d.ts +53 -0
- package/dist/server.js +347 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/clipboard.d.ts +15 -0
- package/dist/tools/clipboard.js +128 -0
- package/dist/tools/clipboard.js.map +1 -0
- package/dist/tools/compound.d.ts +68 -0
- package/dist/tools/compound.js +491 -0
- package/dist/tools/compound.js.map +1 -0
- package/dist/tools/extraction.d.ts +26 -0
- package/dist/tools/extraction.js +414 -0
- package/dist/tools/extraction.js.map +1 -0
- package/dist/tools/frames.d.ts +22 -0
- package/dist/tools/frames.js +165 -0
- package/dist/tools/frames.js.map +1 -0
- package/dist/tools/interaction.d.ts +30 -0
- package/dist/tools/interaction.js +651 -0
- package/dist/tools/interaction.js.map +1 -0
- package/dist/tools/navigation.d.ts +41 -0
- package/dist/tools/navigation.js +316 -0
- package/dist/tools/navigation.js.map +1 -0
- package/dist/tools/network.d.ts +27 -0
- package/dist/tools/network.js +721 -0
- package/dist/tools/network.js.map +1 -0
- package/dist/tools/performance.d.ts +16 -0
- package/dist/tools/performance.js +240 -0
- package/dist/tools/performance.js.map +1 -0
- package/dist/tools/permissions.d.ts +25 -0
- package/dist/tools/permissions.js +308 -0
- package/dist/tools/permissions.js.map +1 -0
- package/dist/tools/service-workers.d.ts +15 -0
- package/dist/tools/service-workers.js +136 -0
- package/dist/tools/service-workers.js.map +1 -0
- package/dist/tools/shadow.d.ts +21 -0
- package/dist/tools/shadow.js +126 -0
- package/dist/tools/shadow.js.map +1 -0
- package/dist/tools/storage.d.ts +30 -0
- package/dist/tools/storage.js +679 -0
- package/dist/tools/storage.js.map +1 -0
- package/dist/tools/structured-extraction.d.ts +22 -0
- package/dist/tools/structured-extraction.js +433 -0
- package/dist/tools/structured-extraction.js.map +1 -0
- package/dist/tools/wait.d.ts +18 -0
- package/dist/tools/wait.js +182 -0
- package/dist/tools/wait.js.map +1 -0
- package/dist/types.d.ts +85 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/extension/background.js +294 -0
- package/extension/content-isolated.js +80 -0
- package/extension/content-main.js +310 -0
- package/extension/icons/icon-128.png +0 -0
- package/extension/icons/icon-48.png +0 -0
- package/extension/icons/icon-96.png +0 -0
- package/extension/manifest.json +39 -0
- package/hooks/session-end.sh +67 -0
- package/hooks/session-start.sh +66 -0
- package/package.json +46 -0
- package/scripts/build-extension.sh +135 -0
- package/scripts/postinstall.sh +91 -0
- package/scripts/preuninstall.sh +25 -0
- package/scripts/update-daemon.sh +62 -0
- package/skills/safari-pilot/SKILL.md +157 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { KillSwitchActiveError } from '../errors.js';
|
|
2
|
+
// ─── KillSwitch ──────────────────────────────────────────────────────────────
|
|
3
|
+
export class KillSwitch {
|
|
4
|
+
_active = false;
|
|
5
|
+
_reason;
|
|
6
|
+
_activatedAt;
|
|
7
|
+
auditLog;
|
|
8
|
+
threshold;
|
|
9
|
+
errorWindow = [];
|
|
10
|
+
constructor(options) {
|
|
11
|
+
this.auditLog = options?.auditLog;
|
|
12
|
+
this.threshold = options?.autoActivation;
|
|
13
|
+
}
|
|
14
|
+
// ── Core API ────────────────────────────────────────────────────────────────
|
|
15
|
+
/**
|
|
16
|
+
* Activate the kill switch. All subsequent `checkBeforeAction` calls will
|
|
17
|
+
* throw `KillSwitchActiveError` until `deactivate` is called.
|
|
18
|
+
*/
|
|
19
|
+
activate(reason) {
|
|
20
|
+
this._active = true;
|
|
21
|
+
this._reason = reason;
|
|
22
|
+
this._activatedAt = new Date().toISOString();
|
|
23
|
+
this.auditLog?.record({
|
|
24
|
+
tool: 'kill_switch',
|
|
25
|
+
tabUrl: '',
|
|
26
|
+
engine: 'applescript',
|
|
27
|
+
params: { action: 'activate', reason },
|
|
28
|
+
result: 'ok',
|
|
29
|
+
elapsed_ms: 0,
|
|
30
|
+
session: 'kill-switch',
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Deactivate the kill switch, re-enabling automation.
|
|
35
|
+
*/
|
|
36
|
+
deactivate() {
|
|
37
|
+
const wasActive = this._active;
|
|
38
|
+
this._active = false;
|
|
39
|
+
this._reason = undefined;
|
|
40
|
+
this._activatedAt = undefined;
|
|
41
|
+
this.errorWindow.length = 0;
|
|
42
|
+
if (wasActive) {
|
|
43
|
+
this.auditLog?.record({
|
|
44
|
+
tool: 'kill_switch',
|
|
45
|
+
tabUrl: '',
|
|
46
|
+
engine: 'applescript',
|
|
47
|
+
params: { action: 'deactivate' },
|
|
48
|
+
result: 'ok',
|
|
49
|
+
elapsed_ms: 0,
|
|
50
|
+
session: 'kill-switch',
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Returns true if the kill switch is currently active.
|
|
56
|
+
*/
|
|
57
|
+
isActive() {
|
|
58
|
+
return this._active;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Returns the current activation state including reason and timestamp.
|
|
62
|
+
*/
|
|
63
|
+
getActivation() {
|
|
64
|
+
if (!this._active) {
|
|
65
|
+
return { active: false };
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
active: true,
|
|
69
|
+
reason: this._reason,
|
|
70
|
+
activatedAt: this._activatedAt,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
// ── Guard ────────────────────────────────────────────────────────────────────
|
|
74
|
+
/**
|
|
75
|
+
* Call before every automation action. Throws `KillSwitchActiveError` if the
|
|
76
|
+
* kill switch is active, otherwise returns normally.
|
|
77
|
+
*/
|
|
78
|
+
checkBeforeAction() {
|
|
79
|
+
if (this._active) {
|
|
80
|
+
throw new KillSwitchActiveError(this._reason ?? 'kill switch activated');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// ── Auto-activation ──────────────────────────────────────────────────────────
|
|
84
|
+
/**
|
|
85
|
+
* Record an error event. If the configured threshold is exceeded within the
|
|
86
|
+
* rolling window, the kill switch auto-activates.
|
|
87
|
+
*/
|
|
88
|
+
recordError() {
|
|
89
|
+
if (!this.threshold)
|
|
90
|
+
return;
|
|
91
|
+
const now = Date.now();
|
|
92
|
+
const windowStart = now - this.threshold.windowSeconds * 1000;
|
|
93
|
+
// Evict timestamps outside the rolling window
|
|
94
|
+
while (this.errorWindow.length > 0 && this.errorWindow[0].at < windowStart) {
|
|
95
|
+
this.errorWindow.shift();
|
|
96
|
+
}
|
|
97
|
+
this.errorWindow.push({ at: now });
|
|
98
|
+
if (this.errorWindow.length >= this.threshold.maxErrors) {
|
|
99
|
+
this.activate(`Auto-activated: ${this.errorWindow.length} errors in ${this.threshold.windowSeconds}s window`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=kill-switch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kill-switch.js","sourceRoot":"","sources":["../../src/security/kill-switch.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface CheckResult {
|
|
2
|
+
allowed: boolean;
|
|
3
|
+
remaining: number;
|
|
4
|
+
resetMs: number;
|
|
5
|
+
}
|
|
6
|
+
export declare class RateLimiter {
|
|
7
|
+
private windows;
|
|
8
|
+
private domainLimits;
|
|
9
|
+
setDomainLimit(domain: string, limit: number): void;
|
|
10
|
+
/**
|
|
11
|
+
* Check if an action is allowed for the given domain.
|
|
12
|
+
* Does NOT record the action — call recordAction() separately.
|
|
13
|
+
*/
|
|
14
|
+
checkLimit(domain: string): CheckResult;
|
|
15
|
+
/**
|
|
16
|
+
* Record an action for the given domain.
|
|
17
|
+
* Throws RateLimitedError if the limit has already been reached.
|
|
18
|
+
*/
|
|
19
|
+
recordAction(domain: string): void;
|
|
20
|
+
/**
|
|
21
|
+
* Returns the count of recorded actions for a domain in the current window.
|
|
22
|
+
*/
|
|
23
|
+
getCount(domain: string): number;
|
|
24
|
+
private limitFor;
|
|
25
|
+
/**
|
|
26
|
+
* Remove entries older than the 60-second window and return the live list.
|
|
27
|
+
* Mutates the map in-place for efficiency.
|
|
28
|
+
*/
|
|
29
|
+
private prune;
|
|
30
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { RateLimitedError } from '../errors.js';
|
|
2
|
+
// ─── RateLimiter ──────────────────────────────────────────────────────────────
|
|
3
|
+
//
|
|
4
|
+
// Sliding-window rate limiter. Tracks action timestamps per domain in the last
|
|
5
|
+
// 60 seconds. Enforces a global default limit and per-domain overrides.
|
|
6
|
+
const WINDOW_MS = 60_000; // 60-second sliding window
|
|
7
|
+
const GLOBAL_DEFAULT_LIMIT = 120; // actions per 60 seconds
|
|
8
|
+
export class RateLimiter {
|
|
9
|
+
// domain → sorted list of timestamp (ms) entries within the window
|
|
10
|
+
windows = new Map();
|
|
11
|
+
// per-domain limit overrides (set from DomainPolicy)
|
|
12
|
+
domainLimits = new Map();
|
|
13
|
+
// ── Configuration ───────────────────────────────────────────────────────────
|
|
14
|
+
setDomainLimit(domain, limit) {
|
|
15
|
+
this.domainLimits.set(domain, limit);
|
|
16
|
+
}
|
|
17
|
+
// ── Core operations ─────────────────────────────────────────────────────────
|
|
18
|
+
/**
|
|
19
|
+
* Check if an action is allowed for the given domain.
|
|
20
|
+
* Does NOT record the action — call recordAction() separately.
|
|
21
|
+
*/
|
|
22
|
+
checkLimit(domain) {
|
|
23
|
+
const now = Date.now();
|
|
24
|
+
const entries = this.prune(domain, now);
|
|
25
|
+
const limit = this.limitFor(domain);
|
|
26
|
+
const count = entries.length;
|
|
27
|
+
const allowed = count < limit;
|
|
28
|
+
const remaining = Math.max(0, limit - count);
|
|
29
|
+
// Time until the oldest entry would fall out of the window
|
|
30
|
+
const resetMs = entries.length > 0 ? Math.max(0, entries[0] + WINDOW_MS - now) : 0;
|
|
31
|
+
return { allowed, remaining, resetMs };
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Record an action for the given domain.
|
|
35
|
+
* Throws RateLimitedError if the limit has already been reached.
|
|
36
|
+
*/
|
|
37
|
+
recordAction(domain) {
|
|
38
|
+
const now = Date.now();
|
|
39
|
+
const entries = this.prune(domain, now);
|
|
40
|
+
const limit = this.limitFor(domain);
|
|
41
|
+
if (entries.length >= limit) {
|
|
42
|
+
throw new RateLimitedError(domain, limit);
|
|
43
|
+
}
|
|
44
|
+
entries.push(now);
|
|
45
|
+
this.windows.set(domain, entries);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Returns the count of recorded actions for a domain in the current window.
|
|
49
|
+
*/
|
|
50
|
+
getCount(domain) {
|
|
51
|
+
const now = Date.now();
|
|
52
|
+
return this.prune(domain, now).length;
|
|
53
|
+
}
|
|
54
|
+
// ── Internal helpers ────────────────────────────────────────────────────────
|
|
55
|
+
limitFor(domain) {
|
|
56
|
+
return this.domainLimits.get(domain) ?? GLOBAL_DEFAULT_LIMIT;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Remove entries older than the 60-second window and return the live list.
|
|
60
|
+
* Mutates the map in-place for efficiency.
|
|
61
|
+
*/
|
|
62
|
+
prune(domain, now) {
|
|
63
|
+
const entries = this.windows.get(domain) ?? [];
|
|
64
|
+
const cutoff = now - WINDOW_MS;
|
|
65
|
+
const pruned = entries.filter((ts) => ts > cutoff);
|
|
66
|
+
this.windows.set(domain, pruned);
|
|
67
|
+
return pruned;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=rate-limiter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limiter.js","sourceRoot":"","sources":["../../src/security/rate-limiter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export interface Rect {
|
|
2
|
+
x: number;
|
|
3
|
+
y: number;
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
}
|
|
7
|
+
export interface IframeInfo {
|
|
8
|
+
src: string;
|
|
9
|
+
rect: Rect;
|
|
10
|
+
crossOrigin: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface PageInfo {
|
|
13
|
+
iframes: IframeInfo[];
|
|
14
|
+
}
|
|
15
|
+
export interface SensitiveRegion {
|
|
16
|
+
rect: Rect;
|
|
17
|
+
reason: string;
|
|
18
|
+
}
|
|
19
|
+
export declare class ScreenshotRedaction {
|
|
20
|
+
/**
|
|
21
|
+
* Analyse page metadata and return regions that must be redacted.
|
|
22
|
+
*
|
|
23
|
+
* Rules applied (in order):
|
|
24
|
+
* - Cross-origin iframes are always flagged — their content cannot be
|
|
25
|
+
* inspected and may contain sensitive third-party UI.
|
|
26
|
+
* - Banking-domain iframes are flagged even if same-origin is claimed,
|
|
27
|
+
* because financial data warrants extra caution.
|
|
28
|
+
*
|
|
29
|
+
* Same-origin, non-banking iframes are considered safe and are not flagged.
|
|
30
|
+
*/
|
|
31
|
+
identifySensitiveRegions(pageInfo: PageInfo): SensitiveRegion[];
|
|
32
|
+
/**
|
|
33
|
+
* Return JavaScript that should be evaluated in the page context before the
|
|
34
|
+
* screenshot is captured.
|
|
35
|
+
*
|
|
36
|
+
* The script locates sensitive elements (cross-origin iframes, password
|
|
37
|
+
* inputs) and temporarily applies `filter: blur(20px)` so the screenshot
|
|
38
|
+
* captures already-blurred content. A cleanup function is attached to
|
|
39
|
+
* `window.__redactionCleanup` for callers to call after the screenshot.
|
|
40
|
+
*/
|
|
41
|
+
getRedactionScript(): string;
|
|
42
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// ─── ScreenshotRedaction ─────────────────────────────────────────────────────
|
|
2
|
+
//
|
|
3
|
+
// Identifies sensitive regions in screenshots that must be redacted before
|
|
4
|
+
// the image is surfaced to downstream consumers.
|
|
5
|
+
//
|
|
6
|
+
// Redaction happens in two stages:
|
|
7
|
+
// 1. `identifySensitiveRegions` — server-side analysis of page metadata
|
|
8
|
+
// (iframe origins, DOM field types) to locate what must be hidden.
|
|
9
|
+
// 2. `getRedactionScript` — client-side JS injected before capture that adds
|
|
10
|
+
// a CSS blur overlay to sensitive elements, so they are blurred in the
|
|
11
|
+
// raw screenshot rather than cropped after the fact.
|
|
12
|
+
// ─── Banking domain patterns ───────────────────────────────────────────────────
|
|
13
|
+
const BANKING_DOMAIN_PATTERNS = [
|
|
14
|
+
/bank(ofamerica|ofengland|west|\.com)/i,
|
|
15
|
+
/chase\.com/i,
|
|
16
|
+
/wellsfargo\.com/i,
|
|
17
|
+
/citibank\.com/i,
|
|
18
|
+
/hsbc\.com/i,
|
|
19
|
+
/barclays\.com/i,
|
|
20
|
+
/paypal\.com/i,
|
|
21
|
+
/stripe\.com/i,
|
|
22
|
+
];
|
|
23
|
+
function isBankingDomain(src) {
|
|
24
|
+
try {
|
|
25
|
+
const hostname = new URL(src).hostname;
|
|
26
|
+
return BANKING_DOMAIN_PATTERNS.some((p) => p.test(hostname));
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
// ─── ScreenshotRedaction ──────────────────────────────────────────────────────
|
|
33
|
+
export class ScreenshotRedaction {
|
|
34
|
+
/**
|
|
35
|
+
* Analyse page metadata and return regions that must be redacted.
|
|
36
|
+
*
|
|
37
|
+
* Rules applied (in order):
|
|
38
|
+
* - Cross-origin iframes are always flagged — their content cannot be
|
|
39
|
+
* inspected and may contain sensitive third-party UI.
|
|
40
|
+
* - Banking-domain iframes are flagged even if same-origin is claimed,
|
|
41
|
+
* because financial data warrants extra caution.
|
|
42
|
+
*
|
|
43
|
+
* Same-origin, non-banking iframes are considered safe and are not flagged.
|
|
44
|
+
*/
|
|
45
|
+
identifySensitiveRegions(pageInfo) {
|
|
46
|
+
const regions = [];
|
|
47
|
+
for (const iframe of pageInfo.iframes) {
|
|
48
|
+
if (iframe.crossOrigin) {
|
|
49
|
+
regions.push({
|
|
50
|
+
rect: iframe.rect,
|
|
51
|
+
reason: 'Cross-origin iframe content cannot be inspected and must be redacted',
|
|
52
|
+
});
|
|
53
|
+
continue; // already flagged — no need for additional banking check
|
|
54
|
+
}
|
|
55
|
+
if (isBankingDomain(iframe.src)) {
|
|
56
|
+
regions.push({
|
|
57
|
+
rect: iframe.rect,
|
|
58
|
+
reason: 'Banking-domain iframe content must be redacted for financial privacy',
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return regions;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Return JavaScript that should be evaluated in the page context before the
|
|
66
|
+
* screenshot is captured.
|
|
67
|
+
*
|
|
68
|
+
* The script locates sensitive elements (cross-origin iframes, password
|
|
69
|
+
* inputs) and temporarily applies `filter: blur(20px)` so the screenshot
|
|
70
|
+
* captures already-blurred content. A cleanup function is attached to
|
|
71
|
+
* `window.__redactionCleanup` for callers to call after the screenshot.
|
|
72
|
+
*/
|
|
73
|
+
getRedactionScript() {
|
|
74
|
+
return `
|
|
75
|
+
(function () {
|
|
76
|
+
var BLUR_STYLE = 'filter: blur(20px) !important; pointer-events: none;';
|
|
77
|
+
var ATTR = 'data-safari-pilot-redacted';
|
|
78
|
+
var affected = [];
|
|
79
|
+
|
|
80
|
+
function redact(el) {
|
|
81
|
+
if (el.hasAttribute(ATTR)) return;
|
|
82
|
+
var prev = el.getAttribute('style') || '';
|
|
83
|
+
el.setAttribute(ATTR, prev);
|
|
84
|
+
el.setAttribute('style', (prev ? prev + '; ' : '') + BLUR_STYLE);
|
|
85
|
+
affected.push(el);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 1. All iframes — cross-origin frames cannot be origin-checked from JS,
|
|
89
|
+
// so redact all iframes as a conservative default.
|
|
90
|
+
var iframes = document.querySelectorAll('iframe');
|
|
91
|
+
for (var i = 0; i < iframes.length; i++) {
|
|
92
|
+
redact(iframes[i]);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 2. Password input fields
|
|
96
|
+
var passwords = document.querySelectorAll('input[type="password"]');
|
|
97
|
+
for (var j = 0; j < passwords.length; j++) {
|
|
98
|
+
redact(passwords[j]);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// 3. Sensitive input fields by name/id attributes
|
|
102
|
+
var sensitiveSelectors = [
|
|
103
|
+
'input[name*="card"]',
|
|
104
|
+
'input[name*="cvv"]',
|
|
105
|
+
'input[name*="ssn"]',
|
|
106
|
+
'input[name*="account"]',
|
|
107
|
+
'input[autocomplete="cc-number"]',
|
|
108
|
+
'input[autocomplete="cc-csc"]',
|
|
109
|
+
].join(', ');
|
|
110
|
+
var sensitiveInputs = document.querySelectorAll(sensitiveSelectors);
|
|
111
|
+
for (var k = 0; k < sensitiveInputs.length; k++) {
|
|
112
|
+
redact(sensitiveInputs[k]);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Cleanup: restore original styles
|
|
116
|
+
window.__redactionCleanup = function () {
|
|
117
|
+
for (var n = 0; n < affected.length; n++) {
|
|
118
|
+
var el = affected[n];
|
|
119
|
+
var orig = el.getAttribute(ATTR) || '';
|
|
120
|
+
if (orig) {
|
|
121
|
+
el.setAttribute('style', orig);
|
|
122
|
+
} else {
|
|
123
|
+
el.removeAttribute('style');
|
|
124
|
+
}
|
|
125
|
+
el.removeAttribute(ATTR);
|
|
126
|
+
}
|
|
127
|
+
affected = [];
|
|
128
|
+
delete window.__redactionCleanup;
|
|
129
|
+
};
|
|
130
|
+
})();
|
|
131
|
+
`.trim();
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=screenshot-redaction.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"screenshot-redaction.js","sourceRoot":"","sources":["../../src/security/screenshot-redaction.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,EAAE;AACF,2EAA2E;AAC3E,iDAAiD;AACjD,EAAE;AACF,mCAAmC;AACnC,0EAA0E;AAC1E,wEAAwE;AACxE,+EAA+E;AAC/E,4EAA4E;AAC5E,0DAA0D;AAwB1D,kFAAkF;AAElF,MAAM,uBAAuB,GAAa;IACxC,uCAAuC;IACvC,aAAa;IACb,kBAAkB;IAClB,gBAAgB;IAChB,YAAY;IACZ,gBAAgB;IAChB,cAAc;IACd,cAAc;CACf,CAAC;AAEF,SAAS,eAAe,CAAC,GAAW;IAClC,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC;QACvC,OAAO,uBAAuB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC/D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,iFAAiF;AAEjF,MAAM,OAAO,mBAAmB;IAC9B;;;;;;;;;;OAUG;IACH,wBAAwB,CAAC,QAAkB;QACzC,MAAM,OAAO,GAAsB,EAAE,CAAC;QAEtC,KAAK,MAAM,MAAM,IAAI,QAAQ,CAAC,OAAO,EAAE,CAAC;YACtC,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;gBACvB,OAAO,CAAC,IAAI,CAAC;oBACX,IAAI,EAAE,MAAM,CAAC,IAAI;oBACjB,MAAM,EAAE,sEAAsE;iBAC/E,CAAC,CAAC;gBACH,SAAS,CAAC,yDAAyD;YACrE,CAAC;YAED,IAAI,eAAe,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;gBAChC,OAAO,CAAC,IAAI,CAAC;oBACX,IAAI,EAAE,MAAM,CAAC,IAAI;oBACjB,MAAM,EAAE,sEAAsE;iBAC/E,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAED,OAAO,OAAO,CAAC;IACjB,CAAC;IAED;;;;;;;;OAQG;IACH,kBAAkB;QAChB,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyDV,CAAC,IAAI,EAAE,CAAC;IACP,CAAC;CACF"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { TabId } from '../types.js';
|
|
2
|
+
export declare class TabOwnership {
|
|
3
|
+
private ownedTabs;
|
|
4
|
+
private preExistingTabs;
|
|
5
|
+
/**
|
|
6
|
+
* Compute a numeric TabId from Safari's 1-based window/tab indices.
|
|
7
|
+
* Formula: windowIndex * 1000 + tabIndex
|
|
8
|
+
*/
|
|
9
|
+
static makeTabId(windowIndex: number, tabIndex: number): TabId;
|
|
10
|
+
/**
|
|
11
|
+
* Record a tab that existed before this agent session started.
|
|
12
|
+
* Pre-existing tabs are NOT agent-owned and cannot be interacted with.
|
|
13
|
+
*/
|
|
14
|
+
recordPreExisting(tabId: TabId): void;
|
|
15
|
+
/**
|
|
16
|
+
* Register a tab as agent-opened. Call this immediately after open_tab.
|
|
17
|
+
*/
|
|
18
|
+
registerTab(tabId: TabId, url: string): void;
|
|
19
|
+
/**
|
|
20
|
+
* Remove a tab from the registry when it is closed.
|
|
21
|
+
*/
|
|
22
|
+
removeTab(tabId: TabId): void;
|
|
23
|
+
/**
|
|
24
|
+
* Update the tracked URL for an owned tab after navigation.
|
|
25
|
+
* No-op if the tab is not owned (avoids silently adopting foreign tabs).
|
|
26
|
+
*/
|
|
27
|
+
updateUrl(tabId: TabId, newUrl: string): void;
|
|
28
|
+
isOwned(tabId: TabId): boolean;
|
|
29
|
+
isPreExisting(tabId: TabId): boolean;
|
|
30
|
+
getUrl(tabId: TabId): string | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Find the TabId for an owned tab by its current URL.
|
|
33
|
+
* Returns undefined if no owned tab matches the URL.
|
|
34
|
+
*/
|
|
35
|
+
findByUrl(url: string): TabId | undefined;
|
|
36
|
+
getOwnedCount(): number;
|
|
37
|
+
getAllOwned(): Array<{
|
|
38
|
+
tabId: TabId;
|
|
39
|
+
url: string;
|
|
40
|
+
}>;
|
|
41
|
+
/**
|
|
42
|
+
* Throws TabNotOwnedError if the tab was not opened by this agent session.
|
|
43
|
+
* Use this as a pre-condition check before every tool that mutates a tab.
|
|
44
|
+
*/
|
|
45
|
+
assertOwnership(tabId: TabId): void;
|
|
46
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { TabNotOwnedError } from '../errors.js';
|
|
2
|
+
// ─── TabOwnership ─────────────────────────────────────────────────────────────
|
|
3
|
+
//
|
|
4
|
+
// Tracks which tabs the agent opened vs. tabs that already existed when the
|
|
5
|
+
// session started. Only agent-owned tabs may be interacted with.
|
|
6
|
+
export class TabOwnership {
|
|
7
|
+
ownedTabs = new Map(); // tabId -> url
|
|
8
|
+
preExistingTabs = new Set();
|
|
9
|
+
// ── Static helpers ──────────────────────────────────────────────────────────
|
|
10
|
+
/**
|
|
11
|
+
* Compute a numeric TabId from Safari's 1-based window/tab indices.
|
|
12
|
+
* Formula: windowIndex * 1000 + tabIndex
|
|
13
|
+
*/
|
|
14
|
+
static makeTabId(windowIndex, tabIndex) {
|
|
15
|
+
return windowIndex * 1000 + tabIndex;
|
|
16
|
+
}
|
|
17
|
+
// ── Session initialisation ──────────────────────────────────────────────────
|
|
18
|
+
/**
|
|
19
|
+
* Record a tab that existed before this agent session started.
|
|
20
|
+
* Pre-existing tabs are NOT agent-owned and cannot be interacted with.
|
|
21
|
+
*/
|
|
22
|
+
recordPreExisting(tabId) {
|
|
23
|
+
this.preExistingTabs.add(tabId);
|
|
24
|
+
}
|
|
25
|
+
// ── Ownership lifecycle ─────────────────────────────────────────────────────
|
|
26
|
+
/**
|
|
27
|
+
* Register a tab as agent-opened. Call this immediately after open_tab.
|
|
28
|
+
*/
|
|
29
|
+
registerTab(tabId, url) {
|
|
30
|
+
this.ownedTabs.set(tabId, url);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Remove a tab from the registry when it is closed.
|
|
34
|
+
*/
|
|
35
|
+
removeTab(tabId) {
|
|
36
|
+
this.ownedTabs.delete(tabId);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Update the tracked URL for an owned tab after navigation.
|
|
40
|
+
* No-op if the tab is not owned (avoids silently adopting foreign tabs).
|
|
41
|
+
*/
|
|
42
|
+
updateUrl(tabId, newUrl) {
|
|
43
|
+
if (this.ownedTabs.has(tabId)) {
|
|
44
|
+
this.ownedTabs.set(tabId, newUrl);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// ── Queries ─────────────────────────────────────────────────────────────────
|
|
48
|
+
isOwned(tabId) {
|
|
49
|
+
return this.ownedTabs.has(tabId);
|
|
50
|
+
}
|
|
51
|
+
isPreExisting(tabId) {
|
|
52
|
+
return this.preExistingTabs.has(tabId);
|
|
53
|
+
}
|
|
54
|
+
getUrl(tabId) {
|
|
55
|
+
return this.ownedTabs.get(tabId);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Find the TabId for an owned tab by its current URL.
|
|
59
|
+
* Returns undefined if no owned tab matches the URL.
|
|
60
|
+
*/
|
|
61
|
+
findByUrl(url) {
|
|
62
|
+
for (const [tabId, tabUrl] of this.ownedTabs) {
|
|
63
|
+
if (tabUrl === url)
|
|
64
|
+
return tabId;
|
|
65
|
+
}
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
getOwnedCount() {
|
|
69
|
+
return this.ownedTabs.size;
|
|
70
|
+
}
|
|
71
|
+
getAllOwned() {
|
|
72
|
+
return Array.from(this.ownedTabs.entries()).map(([tabId, url]) => ({ tabId, url }));
|
|
73
|
+
}
|
|
74
|
+
// ── Guard ────────────────────────────────────────────────────────────────────
|
|
75
|
+
/**
|
|
76
|
+
* Throws TabNotOwnedError if the tab was not opened by this agent session.
|
|
77
|
+
* Use this as a pre-condition check before every tool that mutates a tab.
|
|
78
|
+
*/
|
|
79
|
+
assertOwnership(tabId) {
|
|
80
|
+
if (!this.ownedTabs.has(tabId)) {
|
|
81
|
+
throw new TabNotOwnedError(tabId);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=tab-ownership.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tab-ownership.js","sourceRoot":"","sources":["../../src/security/tab-ownership.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEhD,iFAAiF;AACjF,EAAE;AACF,4EAA4E;AAC5E,iEAAiE;AAEjE,MAAM,OAAO,YAAY;IACf,SAAS,GAAuB,IAAI,GAAG,EAAE,CAAC,CAAC,eAAe;IAC1D,eAAe,GAAe,IAAI,GAAG,EAAE,CAAC;IAEhD,+EAA+E;IAE/E;;;OAGG;IACH,MAAM,CAAC,SAAS,CAAC,WAAmB,EAAE,QAAgB;QACpD,OAAO,WAAW,GAAG,IAAI,GAAG,QAAQ,CAAC;IACvC,CAAC;IAED,+EAA+E;IAE/E;;;OAGG;IACH,iBAAiB,CAAC,KAAY;QAC5B,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAClC,CAAC;IAED,+EAA+E;IAE/E;;OAEG;IACH,WAAW,CAAC,KAAY,EAAE,GAAW;QACnC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IACjC,CAAC;IAED;;OAEG;IACH,SAAS,CAAC,KAAY;QACpB,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;IAED;;;OAGG;IACH,SAAS,CAAC,KAAY,EAAE,MAAc;QACpC,IAAI,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YAC9B,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QACpC,CAAC;IACH,CAAC;IAED,+EAA+E;IAE/E,OAAO,CAAC,KAAY;QAClB,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACnC,CAAC;IAED,aAAa,CAAC,KAAY;QACxB,OAAO,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACzC,CAAC;IAED,MAAM,CAAC,KAAY;QACjB,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACnC,CAAC;IAED;;;OAGG;IACH,SAAS,CAAC,GAAW;QACnB,KAAK,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YAC7C,IAAI,MAAM,KAAK,GAAG;gBAAE,OAAO,KAAK,CAAC;QACnC,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,aAAa;QACX,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;IAC7B,CAAC;IAED,WAAW;QACT,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC;IACtF,CAAC;IAED,gFAAgF;IAEhF;;;OAGG;IACH,eAAe,CAAC,KAAY;QAC1B,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YAC/B,MAAM,IAAI,gBAAgB,CAAC,KAAK,CAAC,CAAC;QACpC,CAAC;IACH,CAAC;CACF"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { Engine, ToolResponse, ToolRequirements } from './types.js';
|
|
2
|
+
import { AppleScriptEngine } from './engines/applescript.js';
|
|
3
|
+
import { KillSwitch } from './security/kill-switch.js';
|
|
4
|
+
import { TabOwnership } from './security/tab-ownership.js';
|
|
5
|
+
import { AuditLog } from './security/audit-log.js';
|
|
6
|
+
import { DomainPolicy } from './security/domain-policy.js';
|
|
7
|
+
import { RateLimiter } from './security/rate-limiter.js';
|
|
8
|
+
import { CircuitBreaker } from './security/circuit-breaker.js';
|
|
9
|
+
import { IdpiScanner } from './security/idpi-scanner.js';
|
|
10
|
+
interface ToolDefinition {
|
|
11
|
+
name: string;
|
|
12
|
+
description: string;
|
|
13
|
+
inputSchema: Record<string, unknown>;
|
|
14
|
+
requirements: ToolRequirements;
|
|
15
|
+
handler: (params: Record<string, unknown>) => Promise<ToolResponse>;
|
|
16
|
+
}
|
|
17
|
+
export declare class SafariPilotServer {
|
|
18
|
+
private tools;
|
|
19
|
+
private engines;
|
|
20
|
+
private engineAvailability;
|
|
21
|
+
private sessionId;
|
|
22
|
+
private _engine;
|
|
23
|
+
readonly killSwitch: KillSwitch;
|
|
24
|
+
readonly tabOwnership: TabOwnership;
|
|
25
|
+
readonly auditLog: AuditLog;
|
|
26
|
+
readonly domainPolicy: DomainPolicy;
|
|
27
|
+
readonly rateLimiter: RateLimiter;
|
|
28
|
+
readonly circuitBreaker: CircuitBreaker;
|
|
29
|
+
readonly idpiScanner: IdpiScanner;
|
|
30
|
+
constructor();
|
|
31
|
+
initialize(): Promise<void>;
|
|
32
|
+
private handleHealthCheck;
|
|
33
|
+
registerTool(tool: ToolDefinition): void;
|
|
34
|
+
getToolNames(): string[];
|
|
35
|
+
callTool(name: string, params: Record<string, unknown>): Promise<ToolResponse>;
|
|
36
|
+
/**
|
|
37
|
+
* Execute a tool through the full security pipeline:
|
|
38
|
+
* kill switch → tab ownership → domain policy → rate limiter →
|
|
39
|
+
* circuit breaker → tool execution → audit log
|
|
40
|
+
*/
|
|
41
|
+
executeToolWithSecurity(name: string, params: Record<string, unknown>): Promise<ToolResponse>;
|
|
42
|
+
getSelectedEngine(requirements: ToolRequirements): Engine;
|
|
43
|
+
setEngineAvailability(availability: {
|
|
44
|
+
daemon: boolean;
|
|
45
|
+
extension: boolean;
|
|
46
|
+
}): void;
|
|
47
|
+
getSessionId(): string;
|
|
48
|
+
getEngine(): AppleScriptEngine | null;
|
|
49
|
+
start(): Promise<void>;
|
|
50
|
+
shutdown(): Promise<void>;
|
|
51
|
+
}
|
|
52
|
+
export declare function createServer(): Promise<SafariPilotServer>;
|
|
53
|
+
export {};
|