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.
Files changed (143) hide show
  1. package/.claude-plugin/plugin.json +35 -0
  2. package/.mcp.json +11 -0
  3. package/LICENSE +21 -0
  4. package/README.md +324 -0
  5. package/bin/.gitkeep +0 -0
  6. package/bin/Safari Pilot.app/Contents/CodeResources +0 -0
  7. package/bin/Safari Pilot.app/Contents/Info.plist +58 -0
  8. package/bin/Safari Pilot.app/Contents/MacOS/Safari Pilot +0 -0
  9. package/bin/Safari Pilot.app/Contents/PkgInfo +1 -0
  10. package/bin/Safari Pilot.app/Contents/PlugIns/Safari Pilot Extension.appex/Contents/Info.plist +55 -0
  11. package/bin/Safari Pilot.app/Contents/PlugIns/Safari Pilot Extension.appex/Contents/MacOS/Safari Pilot Extension +0 -0
  12. package/bin/Safari Pilot.app/Contents/PlugIns/Safari Pilot Extension.appex/Contents/Resources/background.js +294 -0
  13. package/bin/Safari Pilot.app/Contents/PlugIns/Safari Pilot Extension.appex/Contents/Resources/content-isolated.js +80 -0
  14. package/bin/Safari Pilot.app/Contents/PlugIns/Safari Pilot Extension.appex/Contents/Resources/content-main.js +310 -0
  15. package/bin/Safari Pilot.app/Contents/PlugIns/Safari Pilot Extension.appex/Contents/Resources/icons/icon-128.png +0 -0
  16. package/bin/Safari Pilot.app/Contents/PlugIns/Safari Pilot Extension.appex/Contents/Resources/icons/icon-48.png +0 -0
  17. package/bin/Safari Pilot.app/Contents/PlugIns/Safari Pilot Extension.appex/Contents/Resources/icons/icon-96.png +0 -0
  18. package/bin/Safari Pilot.app/Contents/PlugIns/Safari Pilot Extension.appex/Contents/Resources/manifest.json +39 -0
  19. package/bin/Safari Pilot.app/Contents/PlugIns/Safari Pilot Extension.appex/Contents/_CodeSignature/CodeResources +194 -0
  20. package/bin/Safari Pilot.app/Contents/Resources/AppIcon.icns +0 -0
  21. package/bin/Safari Pilot.app/Contents/Resources/Assets.car +0 -0
  22. package/bin/Safari Pilot.app/Contents/Resources/Base.lproj/Main.html +19 -0
  23. package/bin/Safari Pilot.app/Contents/Resources/Base.lproj/Main.storyboardc/Info.plist +0 -0
  24. package/bin/Safari Pilot.app/Contents/Resources/Base.lproj/Main.storyboardc/MainMenu.nib +0 -0
  25. package/bin/Safari Pilot.app/Contents/Resources/Base.lproj/Main.storyboardc/NSWindowController-B8D-0N-5wS.nib +0 -0
  26. package/bin/Safari Pilot.app/Contents/Resources/Base.lproj/Main.storyboardc/XfG-lQ-9wD-view-m2S-Jp-Qdl.nib +0 -0
  27. package/bin/Safari Pilot.app/Contents/Resources/Icon.png +0 -0
  28. package/bin/Safari Pilot.app/Contents/Resources/Script.js +22 -0
  29. package/bin/Safari Pilot.app/Contents/Resources/Style.css +45 -0
  30. package/bin/Safari Pilot.app/Contents/_CodeSignature/CodeResources +236 -0
  31. package/bin/Safari Pilot.zip +0 -0
  32. package/bin/SafariPilotd +0 -0
  33. package/dist/engine-selector.d.ts +10 -0
  34. package/dist/engine-selector.js +55 -0
  35. package/dist/engine-selector.js.map +1 -0
  36. package/dist/engines/applescript.d.ts +53 -0
  37. package/dist/engines/applescript.js +290 -0
  38. package/dist/engines/applescript.js.map +1 -0
  39. package/dist/engines/daemon.d.ts +19 -0
  40. package/dist/engines/daemon.js +187 -0
  41. package/dist/engines/daemon.js.map +1 -0
  42. package/dist/engines/engine.d.ts +15 -0
  43. package/dist/engines/engine.js +42 -0
  44. package/dist/engines/engine.js.map +1 -0
  45. package/dist/engines/extension.d.ts +34 -0
  46. package/dist/engines/extension.js +66 -0
  47. package/dist/engines/extension.js.map +1 -0
  48. package/dist/errors.d.ts +128 -0
  49. package/dist/errors.js +250 -0
  50. package/dist/errors.js.map +1 -0
  51. package/dist/index.d.ts +2 -0
  52. package/dist/index.js +11 -0
  53. package/dist/index.js.map +1 -0
  54. package/dist/security/audit-log.d.ts +23 -0
  55. package/dist/security/audit-log.js +68 -0
  56. package/dist/security/audit-log.js.map +1 -0
  57. package/dist/security/circuit-breaker.d.ts +29 -0
  58. package/dist/security/circuit-breaker.js +114 -0
  59. package/dist/security/circuit-breaker.js.map +1 -0
  60. package/dist/security/domain-policy.d.ts +29 -0
  61. package/dist/security/domain-policy.js +96 -0
  62. package/dist/security/domain-policy.js.map +1 -0
  63. package/dist/security/human-approval.d.ts +20 -0
  64. package/dist/security/human-approval.js +150 -0
  65. package/dist/security/human-approval.js.map +1 -0
  66. package/dist/security/idpi-scanner.d.ts +20 -0
  67. package/dist/security/idpi-scanner.js +102 -0
  68. package/dist/security/idpi-scanner.js.map +1 -0
  69. package/dist/security/kill-switch.d.ts +51 -0
  70. package/dist/security/kill-switch.js +103 -0
  71. package/dist/security/kill-switch.js.map +1 -0
  72. package/dist/security/rate-limiter.d.ts +30 -0
  73. package/dist/security/rate-limiter.js +70 -0
  74. package/dist/security/rate-limiter.js.map +1 -0
  75. package/dist/security/screenshot-redaction.d.ts +42 -0
  76. package/dist/security/screenshot-redaction.js +134 -0
  77. package/dist/security/screenshot-redaction.js.map +1 -0
  78. package/dist/security/tab-ownership.d.ts +46 -0
  79. package/dist/security/tab-ownership.js +85 -0
  80. package/dist/security/tab-ownership.js.map +1 -0
  81. package/dist/server.d.ts +53 -0
  82. package/dist/server.js +347 -0
  83. package/dist/server.js.map +1 -0
  84. package/dist/tools/clipboard.d.ts +15 -0
  85. package/dist/tools/clipboard.js +128 -0
  86. package/dist/tools/clipboard.js.map +1 -0
  87. package/dist/tools/compound.d.ts +68 -0
  88. package/dist/tools/compound.js +491 -0
  89. package/dist/tools/compound.js.map +1 -0
  90. package/dist/tools/extraction.d.ts +26 -0
  91. package/dist/tools/extraction.js +414 -0
  92. package/dist/tools/extraction.js.map +1 -0
  93. package/dist/tools/frames.d.ts +22 -0
  94. package/dist/tools/frames.js +165 -0
  95. package/dist/tools/frames.js.map +1 -0
  96. package/dist/tools/interaction.d.ts +30 -0
  97. package/dist/tools/interaction.js +651 -0
  98. package/dist/tools/interaction.js.map +1 -0
  99. package/dist/tools/navigation.d.ts +41 -0
  100. package/dist/tools/navigation.js +316 -0
  101. package/dist/tools/navigation.js.map +1 -0
  102. package/dist/tools/network.d.ts +27 -0
  103. package/dist/tools/network.js +721 -0
  104. package/dist/tools/network.js.map +1 -0
  105. package/dist/tools/performance.d.ts +16 -0
  106. package/dist/tools/performance.js +240 -0
  107. package/dist/tools/performance.js.map +1 -0
  108. package/dist/tools/permissions.d.ts +25 -0
  109. package/dist/tools/permissions.js +308 -0
  110. package/dist/tools/permissions.js.map +1 -0
  111. package/dist/tools/service-workers.d.ts +15 -0
  112. package/dist/tools/service-workers.js +136 -0
  113. package/dist/tools/service-workers.js.map +1 -0
  114. package/dist/tools/shadow.d.ts +21 -0
  115. package/dist/tools/shadow.js +126 -0
  116. package/dist/tools/shadow.js.map +1 -0
  117. package/dist/tools/storage.d.ts +30 -0
  118. package/dist/tools/storage.js +679 -0
  119. package/dist/tools/storage.js.map +1 -0
  120. package/dist/tools/structured-extraction.d.ts +22 -0
  121. package/dist/tools/structured-extraction.js +433 -0
  122. package/dist/tools/structured-extraction.js.map +1 -0
  123. package/dist/tools/wait.d.ts +18 -0
  124. package/dist/tools/wait.js +182 -0
  125. package/dist/tools/wait.js.map +1 -0
  126. package/dist/types.d.ts +85 -0
  127. package/dist/types.js +2 -0
  128. package/dist/types.js.map +1 -0
  129. package/extension/background.js +294 -0
  130. package/extension/content-isolated.js +80 -0
  131. package/extension/content-main.js +310 -0
  132. package/extension/icons/icon-128.png +0 -0
  133. package/extension/icons/icon-48.png +0 -0
  134. package/extension/icons/icon-96.png +0 -0
  135. package/extension/manifest.json +39 -0
  136. package/hooks/session-end.sh +67 -0
  137. package/hooks/session-start.sh +66 -0
  138. package/package.json +46 -0
  139. package/scripts/build-extension.sh +135 -0
  140. package/scripts/postinstall.sh +91 -0
  141. package/scripts/preuninstall.sh +25 -0
  142. package/scripts/update-daemon.sh +62 -0
  143. package/skills/safari-pilot/SKILL.md +157 -0
@@ -0,0 +1,114 @@
1
+ import { CircuitBreakerOpenError } from '../errors.js';
2
+ const FAILURE_THRESHOLD = 5; // consecutive failures to trip the circuit
3
+ const WINDOW_MS = 60_000; // failure tracking window (60 s)
4
+ const COOLDOWN_MS = 120_000; // open → half-open cooldown (120 s)
5
+ function emptyState() {
6
+ return {
7
+ failures: 0,
8
+ firstFailureAt: 0,
9
+ openedAt: null,
10
+ probeAllowed: false,
11
+ probeInFlight: false,
12
+ };
13
+ }
14
+ export class CircuitBreaker {
15
+ states = new Map();
16
+ // ── Public API ───────────────────────────────────────────────────────────────
17
+ /**
18
+ * Record a successful call. Resets the failure counter and closes the circuit.
19
+ */
20
+ recordSuccess(domain) {
21
+ const state = this.getState_(domain);
22
+ state.failures = 0;
23
+ state.firstFailureAt = 0;
24
+ state.openedAt = null;
25
+ state.probeAllowed = false;
26
+ state.probeInFlight = false;
27
+ this.states.set(domain, state);
28
+ }
29
+ /**
30
+ * Record a failed call. May open the circuit if the threshold is reached.
31
+ */
32
+ recordFailure(domain) {
33
+ const now = Date.now();
34
+ const state = this.getState_(domain);
35
+ // A probe failure in half-open state immediately re-opens the circuit,
36
+ // bypassing the normal threshold, and resets the cooldown clock.
37
+ if (state.probeInFlight) {
38
+ state.openedAt = now;
39
+ state.probeAllowed = false;
40
+ state.probeInFlight = false;
41
+ // Keep failures at threshold so the circuit stays open on next check
42
+ state.failures = FAILURE_THRESHOLD;
43
+ state.firstFailureAt = now;
44
+ this.states.set(domain, state);
45
+ return;
46
+ }
47
+ // Reset failure count if previous run is outside the tracking window
48
+ if (state.failures > 0 && now - state.firstFailureAt > WINDOW_MS) {
49
+ state.failures = 0;
50
+ state.firstFailureAt = 0;
51
+ }
52
+ if (state.failures === 0) {
53
+ state.firstFailureAt = now;
54
+ }
55
+ state.failures += 1;
56
+ if (state.failures >= FAILURE_THRESHOLD) {
57
+ state.openedAt = now;
58
+ state.probeAllowed = false;
59
+ }
60
+ this.states.set(domain, state);
61
+ }
62
+ /**
63
+ * Returns true when the circuit is open (calls should be rejected).
64
+ * Half-open circuits return false — a probe is permitted.
65
+ */
66
+ isOpen(domain) {
67
+ return this.getState(domain) === 'open';
68
+ }
69
+ /**
70
+ * Compute the current circuit state for a domain.
71
+ */
72
+ getState(domain) {
73
+ const state = this.getState_(domain);
74
+ if (state.openedAt === null) {
75
+ return 'closed';
76
+ }
77
+ const elapsed = Date.now() - state.openedAt;
78
+ if (elapsed >= COOLDOWN_MS) {
79
+ return 'half-open';
80
+ }
81
+ return 'open';
82
+ }
83
+ /**
84
+ * Assert the circuit is not open before executing a call.
85
+ * Call this at the entry point of any guarded operation.
86
+ * Throws CircuitBreakerOpenError when the circuit is open.
87
+ * In half-open state, marks the probe as issued and allows one call through.
88
+ */
89
+ assertClosed(domain) {
90
+ const circuitState = this.getState(domain);
91
+ const state = this.getState_(domain);
92
+ if (circuitState === 'open') {
93
+ const remaining = COOLDOWN_MS - (Date.now() - (state.openedAt ?? 0));
94
+ throw new CircuitBreakerOpenError(domain, Math.ceil(remaining / 1000));
95
+ }
96
+ if (circuitState === 'half-open') {
97
+ if (state.probeAllowed) {
98
+ // Already issued a probe — reject subsequent calls until success/fail
99
+ throw new CircuitBreakerOpenError(domain, 0);
100
+ }
101
+ state.probeAllowed = true;
102
+ state.probeInFlight = true;
103
+ this.states.set(domain, state);
104
+ }
105
+ }
106
+ // ── Internal ─────────────────────────────────────────────────────────────────
107
+ getState_(domain) {
108
+ if (!this.states.has(domain)) {
109
+ this.states.set(domain, emptyState());
110
+ }
111
+ return this.states.get(domain);
112
+ }
113
+ }
114
+ //# sourceMappingURL=circuit-breaker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"circuit-breaker.js","sourceRoot":"","sources":["../../src/security/circuit-breaker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ type TrustLevel = 'trusted' | 'untrusted' | 'unknown';
2
+ interface PolicyRule {
3
+ trust: TrustLevel;
4
+ privateWindow: boolean;
5
+ extensionAllowed: boolean;
6
+ maxActionsPerMinute: number;
7
+ }
8
+ export interface EvaluateResult {
9
+ trust: TrustLevel;
10
+ privateWindow: boolean;
11
+ extensionAllowed: boolean;
12
+ maxActionsPerMinute: number;
13
+ }
14
+ export declare class DomainPolicy {
15
+ private rules;
16
+ constructor();
17
+ addRule(domain: string, policy: Partial<PolicyRule>): void;
18
+ removeRule(domain: string): void;
19
+ /**
20
+ * Evaluate a URL against all stored rules.
21
+ * Rules are checked in insertion order; first match wins.
22
+ * Falls back to DEFAULT_POLICY if no rule matches.
23
+ */
24
+ evaluate(url: string): EvaluateResult;
25
+ getRules(): Array<{
26
+ domain: string;
27
+ } & PolicyRule>;
28
+ }
29
+ export {};
@@ -0,0 +1,96 @@
1
+ const DEFAULT_POLICY = {
2
+ trust: 'unknown',
3
+ privateWindow: false,
4
+ extensionAllowed: false,
5
+ maxActionsPerMinute: 60,
6
+ };
7
+ // Built-in sensitive domain patterns → untrusted, force private window
8
+ const SENSITIVE_PATTERNS = [
9
+ '*.bank.*',
10
+ '*.banking.*',
11
+ 'paypal.com',
12
+ '*.paypal.com',
13
+ 'stripe.com',
14
+ '*.stripe.com',
15
+ 'venmo.com',
16
+ '*.venmo.com',
17
+ 'chase.com',
18
+ '*.chase.com',
19
+ 'wellsfargo.com',
20
+ '*.wellsfargo.com',
21
+ 'bankofamerica.com',
22
+ '*.bankofamerica.com',
23
+ 'citibank.com',
24
+ '*.citibank.com',
25
+ ];
26
+ const SENSITIVE_POLICY = {
27
+ trust: 'untrusted',
28
+ privateWindow: true,
29
+ extensionAllowed: false,
30
+ maxActionsPerMinute: 30,
31
+ };
32
+ // ── Glob matching ─────────────────────────────────────────────────────────────
33
+ /**
34
+ * Convert a glob pattern (only * wildcard supported) to a RegExp.
35
+ * *.example.com matches sub.example.com but NOT example.com itself.
36
+ */
37
+ function globToRegex(pattern) {
38
+ const escaped = pattern
39
+ .split('*')
40
+ .map((part) => part.replace(/[.+^${}()|[\]\\]/g, '\\$&'))
41
+ .join('[^.]+');
42
+ return new RegExp(`^${escaped}$`, 'i');
43
+ }
44
+ function extractHostname(url) {
45
+ try {
46
+ return new URL(url).hostname.toLowerCase();
47
+ }
48
+ catch {
49
+ // If it's already a hostname (no scheme), return as-is
50
+ return url.toLowerCase();
51
+ }
52
+ }
53
+ function matchesDomain(pattern, hostname) {
54
+ if (pattern.includes('*')) {
55
+ return globToRegex(pattern).test(hostname);
56
+ }
57
+ return pattern.toLowerCase() === hostname;
58
+ }
59
+ // ─── DomainPolicy class ───────────────────────────────────────────────────────
60
+ export class DomainPolicy {
61
+ rules = new Map();
62
+ constructor() {
63
+ // Register built-in sensitive domain rules
64
+ for (const pattern of SENSITIVE_PATTERNS) {
65
+ this.rules.set(pattern, { ...SENSITIVE_POLICY });
66
+ }
67
+ }
68
+ // ── Rule management ─────────────────────────────────────────────────────────
69
+ addRule(domain, policy) {
70
+ const existing = this.rules.get(domain) ?? { ...DEFAULT_POLICY };
71
+ this.rules.set(domain, { ...existing, ...policy });
72
+ }
73
+ removeRule(domain) {
74
+ this.rules.delete(domain);
75
+ }
76
+ // ── Evaluation ──────────────────────────────────────────────────────────────
77
+ /**
78
+ * Evaluate a URL against all stored rules.
79
+ * Rules are checked in insertion order; first match wins.
80
+ * Falls back to DEFAULT_POLICY if no rule matches.
81
+ */
82
+ evaluate(url) {
83
+ const hostname = extractHostname(url);
84
+ for (const [pattern, rule] of this.rules) {
85
+ if (matchesDomain(pattern, hostname)) {
86
+ return { ...rule };
87
+ }
88
+ }
89
+ return { ...DEFAULT_POLICY };
90
+ }
91
+ // ── Introspection ────────────────────────────────────────────────────────────
92
+ getRules() {
93
+ return Array.from(this.rules.entries()).map(([domain, rule]) => ({ domain, ...rule }));
94
+ }
95
+ }
96
+ //# sourceMappingURL=domain-policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"domain-policy.js","sourceRoot":"","sources":["../../src/security/domain-policy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ export interface ApprovalResult {
2
+ required: boolean;
3
+ reason?: string;
4
+ category?: string;
5
+ }
6
+ export declare class HumanApproval {
7
+ /**
8
+ * Inspect an action/url/params combination and return whether human approval
9
+ * is required. Returns `{ required: false }` for benign actions, or
10
+ * `{ required: true, reason, category }` for sensitive ones.
11
+ *
12
+ * Does NOT throw — use `assertApproved` for a throwing guard.
13
+ */
14
+ requiresApproval(action: string, url: string, params?: Record<string, unknown>): ApprovalResult;
15
+ /**
16
+ * Guard variant: throws `HumanApprovalRequiredError` when approval is needed.
17
+ * Use in tool pipelines where blocking is the correct behaviour.
18
+ */
19
+ assertApproved(action: string, url: string, params?: Record<string, unknown>): void;
20
+ }
@@ -0,0 +1,150 @@
1
+ import { HumanApprovalRequiredError } from '../errors.js';
2
+ // ─── Pattern Registry ─────────────────────────────────────────────────────────
3
+ /** OAuth / SSO provider URL patterns. */
4
+ const OAUTH_URL_PATTERNS = [
5
+ /accounts\.google\.com\/o\/oauth/i,
6
+ /login\.microsoftonline\.com/i,
7
+ /github\.com\/login\/oauth/i,
8
+ /auth0\.com/i,
9
+ /okta\.com/i,
10
+ /login\.live\.com/i,
11
+ /appleid\.apple\.com/i,
12
+ /facebook\.com\/dialog\/oauth/i,
13
+ ];
14
+ /** Financial checkout / payment URL patterns. */
15
+ const FINANCIAL_URL_PATTERNS = [
16
+ /paypal\.com\/checkout/i,
17
+ /paypal\.com\/pay/i,
18
+ /stripe\.com\/pay/i,
19
+ /checkout\.stripe\.com/i,
20
+ /pay\.amazon\.com/i,
21
+ /venmo\.com\/pay/i,
22
+ ];
23
+ /** Sensitive financial form field names. */
24
+ const FINANCIAL_FIELD_NAMES = [
25
+ /^card[_-]?number$/i,
26
+ /^cvv$/i,
27
+ /^cvc$/i,
28
+ /^account[_-]?number$/i,
29
+ /^routing[_-]?number$/i,
30
+ /^bank[_-]?account$/i,
31
+ /^credit[_-]?card$/i,
32
+ ];
33
+ /** Downloadable file extensions that require approval. */
34
+ const DOWNLOAD_EXTENSIONS = /\.(exe|dmg|pkg|zip|tar|gz|rar|deb|rpm|msi|app|apk)(\?.*)?$/i;
35
+ /** Account-mutating / high-risk URL path patterns. */
36
+ const ACCOUNT_SETTINGS_PATTERNS = [
37
+ /\/settings\/security/i,
38
+ /\/account\/delete/i,
39
+ /\/password\/change/i,
40
+ /\/password\/reset/i,
41
+ /\/delete[_-]?account/i,
42
+ /\/security\/two-factor/i,
43
+ ];
44
+ /** Sensitive field names in form submissions. */
45
+ const SENSITIVE_FORM_FIELDS = [
46
+ /^password$/i,
47
+ /^passwd$/i,
48
+ /^pass$/i,
49
+ /^ssn$/i,
50
+ /^social[_-]?security/i,
51
+ /^credit[_-]?card/i,
52
+ /^card[_-]?number/i,
53
+ /^cvv$/i,
54
+ /^cvc$/i,
55
+ ];
56
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
57
+ function matchesAny(value, patterns) {
58
+ return patterns.some((p) => p.test(value));
59
+ }
60
+ function extractDomain(url) {
61
+ try {
62
+ return new URL(url).hostname;
63
+ }
64
+ catch {
65
+ return url;
66
+ }
67
+ }
68
+ // ─── HumanApproval ───────────────────────────────────────────────────────────
69
+ export class HumanApproval {
70
+ /**
71
+ * Inspect an action/url/params combination and return whether human approval
72
+ * is required. Returns `{ required: false }` for benign actions, or
73
+ * `{ required: true, reason, category }` for sensitive ones.
74
+ *
75
+ * Does NOT throw — use `assertApproved` for a throwing guard.
76
+ */
77
+ requiresApproval(action, url, params) {
78
+ // 1. OAuth / SSO flows
79
+ if (matchesAny(url, OAUTH_URL_PATTERNS)) {
80
+ return {
81
+ required: true,
82
+ category: 'oauth',
83
+ reason: 'URL matches an OAuth/SSO authentication flow',
84
+ };
85
+ }
86
+ // 2. Financial checkout / payment pages
87
+ if (matchesAny(url, FINANCIAL_URL_PATTERNS)) {
88
+ return {
89
+ required: true,
90
+ category: 'financial',
91
+ reason: 'URL matches a financial payment or checkout flow',
92
+ };
93
+ }
94
+ // 3. Financial form fields in params
95
+ if (params !== undefined) {
96
+ const fieldNames = Object.keys(params);
97
+ const sensitiveField = fieldNames.find((f) => matchesAny(f, FINANCIAL_FIELD_NAMES));
98
+ if (sensitiveField !== undefined) {
99
+ return {
100
+ required: true,
101
+ category: 'financial',
102
+ reason: `Request contains sensitive financial field: "${sensitiveField}"`,
103
+ };
104
+ }
105
+ }
106
+ // 4. Download actions or downloadable file extensions in URL
107
+ if (action === 'download' || DOWNLOAD_EXTENSIONS.test(url)) {
108
+ return {
109
+ required: true,
110
+ category: 'download',
111
+ reason: 'Action involves downloading a file',
112
+ };
113
+ }
114
+ // 5. Account settings / destructive account operations
115
+ if (matchesAny(url, ACCOUNT_SETTINGS_PATTERNS)) {
116
+ return {
117
+ required: true,
118
+ category: 'account_settings',
119
+ reason: 'URL matches a sensitive account settings or deletion path',
120
+ };
121
+ }
122
+ // 6. Form submissions (POST) with sensitive field names
123
+ if (action === 'submit' || action === 'post') {
124
+ if (params !== undefined) {
125
+ const fieldNames = Object.keys(params);
126
+ const sensitiveField = fieldNames.find((f) => matchesAny(f, SENSITIVE_FORM_FIELDS));
127
+ if (sensitiveField !== undefined) {
128
+ return {
129
+ required: true,
130
+ category: 'form_submission',
131
+ reason: `Form submission contains sensitive field: "${sensitiveField}"`,
132
+ };
133
+ }
134
+ }
135
+ }
136
+ return { required: false };
137
+ }
138
+ /**
139
+ * Guard variant: throws `HumanApprovalRequiredError` when approval is needed.
140
+ * Use in tool pipelines where blocking is the correct behaviour.
141
+ */
142
+ assertApproved(action, url, params) {
143
+ const result = this.requiresApproval(action, url, params);
144
+ if (result.required) {
145
+ const domain = extractDomain(url);
146
+ throw new HumanApprovalRequiredError(action, domain);
147
+ }
148
+ }
149
+ }
150
+ //# sourceMappingURL=human-approval.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"human-approval.js","sourceRoot":"","sources":["../../src/security/human-approval.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ export interface IdpiThreat {
2
+ pattern: string;
3
+ confidence: number;
4
+ match: string;
5
+ }
6
+ export interface ScanResult {
7
+ safe: boolean;
8
+ threats: IdpiThreat[];
9
+ }
10
+ export declare class IdpiScanner {
11
+ /**
12
+ * Scan a text string for indirect prompt injection patterns.
13
+ *
14
+ * Returns a result with:
15
+ * - `safe` — false when any threat confidence exceeds 0.5
16
+ * - `threats` — all matched threats with their pattern name, confidence, and
17
+ * the excerpt that triggered the match
18
+ */
19
+ scan(text: string): ScanResult;
20
+ }
@@ -0,0 +1,102 @@
1
+ // ─── IdpiScanner ─────────────────────────────────────────────────────────────
2
+ //
3
+ // Indirect Prompt Injection (IDPI) defence layer. Scans arbitrary text content
4
+ // retrieved from web pages for patterns that attempt to hijack agent behaviour.
5
+ //
6
+ // Each detector is a named rule with a confidence weight (0.0–1.0). A result
7
+ // is marked unsafe when any matched threat has confidence > 0.5.
8
+ // ─── Pattern Registry ─────────────────────────────────────────────────────────
9
+ const PATTERN_RULES = [
10
+ // 1. Instruction override attempts
11
+ {
12
+ name: 'instruction_override',
13
+ regex: /ignore\s+(previous|prior|all\s+prior|above)\s+instructions?|disregard\s+(the\s+)?(above|previous|prior)/gi,
14
+ confidence: 0.95,
15
+ },
16
+ // 2. Role reassignment attempts
17
+ {
18
+ name: 'role_reassignment',
19
+ regex: /you\s+are\s+now\s+(a|an)\s+\w+|you\s+are\s+a\s+\w+/gi,
20
+ confidence: 0.80,
21
+ },
22
+ // 3. Fake system prompt injection
23
+ {
24
+ name: 'fake_system_prompt',
25
+ regex: /^system:|^SYSTEM:|###\s*System\b/gim,
26
+ confidence: 0.90,
27
+ },
28
+ // 4. Base64-encoded payloads (50+ contiguous base64 chars)
29
+ {
30
+ name: 'base64_payload',
31
+ regex: /[A-Za-z0-9+/]{50,}={0,2}/g,
32
+ confidence: 0.65,
33
+ },
34
+ // 5. Secrecy / concealment instructions
35
+ {
36
+ name: 'secrecy_instruction',
37
+ regex: /do\s+not\s+tell\s+(the\s+)?user|keep\s+this\s+secret|don['']?t\s+mention\s+this/gi,
38
+ confidence: 0.90,
39
+ },
40
+ // 6. HTML-encoded instruction sequences (&#x...; or &#...;)
41
+ {
42
+ name: 'html_encoded_instruction',
43
+ regex: /(?:&#x[0-9a-fA-F]{2,4};){4,}|(?:&#\d{2,5};){4,}/g,
44
+ confidence: 0.75,
45
+ },
46
+ // 7. CSS content property with non-trivial text
47
+ {
48
+ name: 'css_content_injection',
49
+ regex: /content\s*:\s*["'][^"']{10,}["']/gi,
50
+ confidence: 0.70,
51
+ },
52
+ // 8. Hidden text patterns via CSS
53
+ {
54
+ name: 'hidden_text',
55
+ regex: /display\s*:\s*none|visibility\s*:\s*hidden|font-size\s*:\s*0(px|pt|em|rem)?/gi,
56
+ confidence: 0.60,
57
+ },
58
+ // 9. Unicode homoglyph attacks — non-ASCII characters mixed into Latin words
59
+ {
60
+ name: 'unicode_homoglyph',
61
+ regex: /[^\x00-\x7F\s]{1}[a-zA-Z]{2,}|[a-zA-Z]{2,}[^\x00-\x7F\s]{1}/g,
62
+ confidence: 0.75,
63
+ },
64
+ ];
65
+ // ─── IdpiScanner ─────────────────────────────────────────────────────────────
66
+ export class IdpiScanner {
67
+ /**
68
+ * Scan a text string for indirect prompt injection patterns.
69
+ *
70
+ * Returns a result with:
71
+ * - `safe` — false when any threat confidence exceeds 0.5
72
+ * - `threats` — all matched threats with their pattern name, confidence, and
73
+ * the excerpt that triggered the match
74
+ */
75
+ scan(text) {
76
+ const threats = [];
77
+ for (const rule of PATTERN_RULES) {
78
+ // Reset lastIndex so repeated calls work correctly on global regexes
79
+ rule.regex.lastIndex = 0;
80
+ let match;
81
+ const seen = new Set();
82
+ while ((match = rule.regex.exec(text)) !== null) {
83
+ const excerpt = match[0].slice(0, 100); // cap excerpt length
84
+ if (seen.has(excerpt))
85
+ continue; // deduplicate identical matches
86
+ seen.add(excerpt);
87
+ threats.push({
88
+ pattern: rule.name,
89
+ confidence: rule.confidence,
90
+ match: excerpt,
91
+ });
92
+ // Prevent infinite loops on zero-length matches
93
+ if (match.index === rule.regex.lastIndex) {
94
+ rule.regex.lastIndex++;
95
+ }
96
+ }
97
+ }
98
+ const safe = threats.every((t) => t.confidence <= 0.5);
99
+ return { safe, threats };
100
+ }
101
+ }
102
+ //# sourceMappingURL=idpi-scanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"idpi-scanner.js","sourceRoot":"","sources":["../../src/security/idpi-scanner.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,EAAE;AACF,+EAA+E;AAC/E,gFAAgF;AAChF,EAAE;AACF,6EAA6E;AAC7E,iEAAiE;AAmBjE,iFAAiF;AAEjF,MAAM,aAAa,GAAkB;IACnC,mCAAmC;IACnC;QACE,IAAI,EAAE,sBAAsB;QAC5B,KAAK,EAAE,2GAA2G;QAClH,UAAU,EAAE,IAAI;KACjB;IAED,gCAAgC;IAChC;QACE,IAAI,EAAE,mBAAmB;QACzB,KAAK,EAAE,sDAAsD;QAC7D,UAAU,EAAE,IAAI;KACjB;IAED,kCAAkC;IAClC;QACE,IAAI,EAAE,oBAAoB;QAC1B,KAAK,EAAE,qCAAqC;QAC5C,UAAU,EAAE,IAAI;KACjB;IAED,2DAA2D;IAC3D;QACE,IAAI,EAAE,gBAAgB;QACtB,KAAK,EAAE,2BAA2B;QAClC,UAAU,EAAE,IAAI;KACjB;IAED,wCAAwC;IACxC;QACE,IAAI,EAAE,qBAAqB;QAC3B,KAAK,EAAE,mFAAmF;QAC1F,UAAU,EAAE,IAAI;KACjB;IAED,4DAA4D;IAC5D;QACE,IAAI,EAAE,0BAA0B;QAChC,KAAK,EAAE,kDAAkD;QACzD,UAAU,EAAE,IAAI;KACjB;IAED,gDAAgD;IAChD;QACE,IAAI,EAAE,uBAAuB;QAC7B,KAAK,EAAE,oCAAoC;QAC3C,UAAU,EAAE,IAAI;KACjB;IAED,kCAAkC;IAClC;QACE,IAAI,EAAE,aAAa;QACnB,KAAK,EAAE,+EAA+E;QACtF,UAAU,EAAE,IAAI;KACjB;IAED,6EAA6E;IAC7E;QACE,IAAI,EAAE,mBAAmB;QACzB,KAAK,EAAE,8DAA8D;QACrE,UAAU,EAAE,IAAI;KACjB;CACF,CAAC;AAEF,gFAAgF;AAEhF,MAAM,OAAO,WAAW;IACtB;;;;;;;OAOG;IACH,IAAI,CAAC,IAAY;QACf,MAAM,OAAO,GAAiB,EAAE,CAAC;QAEjC,KAAK,MAAM,IAAI,IAAI,aAAa,EAAE,CAAC;YACjC,qEAAqE;YACrE,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC;YAEzB,IAAI,KAA6B,CAAC;YAClC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;YAE/B,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;gBAChD,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,qBAAqB;gBAC7D,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC;oBAAE,SAAS,CAAC,gCAAgC;gBACjE,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;gBAElB,OAAO,CAAC,IAAI,CAAC;oBACX,OAAO,EAAE,IAAI,CAAC,IAAI;oBAClB,UAAU,EAAE,IAAI,CAAC,UAAU;oBAC3B,KAAK,EAAE,OAAO;iBACf,CAAC,CAAC;gBAEH,gDAAgD;gBAChD,IAAI,KAAK,CAAC,KAAK,KAAK,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC;oBACzC,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC;gBACzB,CAAC;YACH,CAAC;QACH,CAAC;QAED,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,IAAI,GAAG,CAAC,CAAC;QAEvD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IAC3B,CAAC;CACF"}
@@ -0,0 +1,51 @@
1
+ import { AuditLog } from './audit-log.js';
2
+ export interface ActivationState {
3
+ active: boolean;
4
+ reason?: string;
5
+ activatedAt?: string;
6
+ }
7
+ export interface AutoActivationThreshold {
8
+ /** Number of errors that triggers auto-activation. */
9
+ maxErrors: number;
10
+ /** Rolling window (seconds) within which errors are counted. */
11
+ windowSeconds: number;
12
+ }
13
+ export declare class KillSwitch {
14
+ private _active;
15
+ private _reason;
16
+ private _activatedAt;
17
+ private readonly auditLog;
18
+ private readonly threshold;
19
+ private readonly errorWindow;
20
+ constructor(options?: {
21
+ auditLog?: AuditLog;
22
+ autoActivation?: AutoActivationThreshold;
23
+ });
24
+ /**
25
+ * Activate the kill switch. All subsequent `checkBeforeAction` calls will
26
+ * throw `KillSwitchActiveError` until `deactivate` is called.
27
+ */
28
+ activate(reason: string): void;
29
+ /**
30
+ * Deactivate the kill switch, re-enabling automation.
31
+ */
32
+ deactivate(): void;
33
+ /**
34
+ * Returns true if the kill switch is currently active.
35
+ */
36
+ isActive(): boolean;
37
+ /**
38
+ * Returns the current activation state including reason and timestamp.
39
+ */
40
+ getActivation(): ActivationState;
41
+ /**
42
+ * Call before every automation action. Throws `KillSwitchActiveError` if the
43
+ * kill switch is active, otherwise returns normally.
44
+ */
45
+ checkBeforeAction(): void;
46
+ /**
47
+ * Record an error event. If the configured threshold is exceeded within the
48
+ * rolling window, the kill switch auto-activates.
49
+ */
50
+ recordError(): void;
51
+ }