@hybridaione/hybridclaw 0.1.19 → 0.1.20

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/.env.example CHANGED
@@ -5,3 +5,10 @@ HYBRIDAI_API_KEY=
5
5
  DISCORD_TOKEN= # Enable Discord integration when set
6
6
  WEB_API_TOKEN= # Protect /api/* endpoints (Bearer token)
7
7
  GATEWAY_API_TOKEN= # Client token override (defaults to WEB_API_TOKEN)
8
+
9
+ # Optional browser settings
10
+ BROWSER_ALLOW_PRIVATE_NETWORK= # Set true to allow localhost/private network navigation in browser tools
11
+ BROWSER_PERSIST_PROFILE= # Default true; set false for ephemeral browser profile
12
+ BROWSER_PERSIST_SESSION_STATE= # Default true; set false to disable auto save/restore state files
13
+ BROWSER_PROFILE_ROOT= # Optional profile root path (default: /workspace/.hybridclaw-runtime/browser-profiles in container)
14
+ BROWSER_CDP_URL= # Optional CDP endpoint (e.g. ws://127.0.0.1:9222/devtools/browser/...)
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "imageName": "hybridclaw-agent",
3
- "fingerprint": "dd50e47197b2010c0b7e61fed0dd603106b5249d99f6a7fe846039932d2d53c6",
4
- "recordedAt": "2026-03-02T17:02:41.340Z"
3
+ "fingerprint": "3d8807c236f660de18d8e98a9539ca1862a2ed522f1a9acf9de862c5d12c4c75",
4
+ "recordedAt": "2026-03-02T20:30:27.276Z"
5
5
  }
package/CHANGELOG.md CHANGED
@@ -8,6 +8,22 @@
8
8
 
9
9
  ### Fixed
10
10
 
11
+ ## [0.1.20](https://github.com/HybridAIOne/hybridclaw/tree/v0.1.20)
12
+
13
+ ### Added
14
+
15
+ - **Browser auth policy clarification**: Added explicit runtime guidance that user-directed login/auth-flow testing is allowed with browser tools on the requested domain.
16
+
17
+ ### Changed
18
+
19
+ - **Persistent browser login continuity**: Browser tooling now persists per-session profile/state by default (`AGENT_BROWSER_PROFILE` + `AGENT_BROWSER_SESSION_NAME`) with configurable overrides (`BROWSER_PERSIST_PROFILE`, `BROWSER_PERSIST_SESSION_STATE`, `BROWSER_PROFILE_ROOT`, `BROWSER_CDP_URL`).
20
+ - **Safety prompt alignment**: System safety hook now explicitly rejects fabricated “public-only/unauthenticated browser” limitations and prioritizes real tool/policy outcomes.
21
+ - **Documentation refresh**: Updated README and website docs (`docs/index.html`) with authenticated browser-flow support and browser session persistence behavior.
22
+
23
+ ### Fixed
24
+
25
+ - **Audit secret leakage risk**: Structured audit tool-call arguments now redact sensitive fields (password/token/secret/etc.), including `browser_type.text`, to avoid credential plaintext in audit trails.
26
+
11
27
  ## [0.1.19](https://github.com/HybridAIOne/hybridclaw/tree/v0.1.19)
12
28
 
13
29
  ### Added
package/README.md CHANGED
@@ -246,6 +246,11 @@ Browser tooling notes:
246
246
 
247
247
  - The shipped container image preinstalls `agent-browser` and Chromium (Playwright).
248
248
  - You can override the binary via `AGENT_BROWSER_BIN` if needed.
249
+ - User-directed authenticated browser-flow testing is supported (including filling/submitting login forms on the requested site).
250
+ - Browser auth/session state now persists per HybridClaw session by default via a dedicated profile directory under `/workspace/.hybridclaw-runtime/browser-profiles`.
251
+ - Session cookies/localStorage are also auto-saved/restored via `agent-browser` session-state files.
252
+ - Optional overrides: `BROWSER_PERSIST_PROFILE=false` (disable profile persistence), `BROWSER_PERSIST_SESSION_STATE=false` (disable state file persistence), `BROWSER_PROFILE_ROOT=/path` (custom profile root), `BROWSER_CDP_URL=ws://...` (force CDP attachment to an existing browser).
253
+ - Structured audit logs redact sensitive browser/tool arguments (password/token/secret fields and typed form text).
249
254
  - Navigation to private/loopback hosts is blocked by default (set `BROWSER_ALLOW_PRIVATE_NETWORK=true` to override).
250
255
  - Screenshot/PDF outputs are constrained to `/workspace/.browser-artifacts`.
251
256
 
package/SECURITY.md CHANGED
@@ -22,6 +22,14 @@ System prompts include safety constraints for every conversation turn:
22
22
 
23
23
  Implementation: [src/prompt-hooks.ts](./src/prompt-hooks.ts)
24
24
 
25
+ ### 1.1) Browser Authentication Flows
26
+
27
+ User-directed browser authentication testing is permitted when the user explicitly asks for it:
28
+
29
+ - Browser tools may fill credentials and submit login forms for the requested site.
30
+ - Credentials must be used only for the requested auth flow on the intended domain.
31
+ - Credentials must not be echoed in assistant prose, written to workspace files, or sent to unrelated domains.
32
+
25
33
  ### 2) Runtime Tool Blocking
26
34
 
27
35
  Before tool execution, HybridClaw applies policy hooks that block known dangerous patterns:
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "hybridclaw-agent",
3
- "version": "0.1.19",
3
+ "version": "0.1.20",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "hybridclaw-agent",
9
- "version": "0.1.19",
9
+ "version": "0.1.20",
10
10
  "dependencies": {
11
11
  "@mozilla/readability": "^0.6.0",
12
12
  "agent-browser": "^0.15.1",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hybridclaw-agent",
3
- "version": "0.1.19",
3
+ "version": "0.1.20",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "build": "tsc",
@@ -1,4 +1,5 @@
1
1
  import { execFile, spawnSync } from 'child_process';
2
+ import { createHash } from 'crypto';
2
3
  import { lookup } from 'dns/promises';
3
4
  import fs from 'fs';
4
5
  import net from 'net';
@@ -19,6 +20,8 @@ const BROWSER_TMP_HOME = path.join(BROWSER_RUNTIME_ROOT, 'home');
19
20
  const BROWSER_NPM_CACHE = path.join(BROWSER_RUNTIME_ROOT, 'npm-cache');
20
21
  const BROWSER_XDG_CACHE = path.join(BROWSER_RUNTIME_ROOT, 'cache');
21
22
  const BROWSER_PLAYWRIGHT_CACHE = path.join(BROWSER_RUNTIME_ROOT, 'ms-playwright');
23
+ const BROWSER_PROFILE_ROOT = path.join(BROWSER_RUNTIME_ROOT, 'browser-profiles');
24
+ const ENV_FALSEY = new Set(['0', 'false', 'no', 'off']);
22
25
 
23
26
  type BrowserRunner = {
24
27
  cmd: string;
@@ -28,6 +31,8 @@ type BrowserRunner = {
28
31
  type BrowserSession = {
29
32
  sessionKey: string;
30
33
  socketDir: string;
34
+ profileDir?: string;
35
+ stateName?: string;
31
36
  createdAt: number;
32
37
  lastUsedAt: number;
33
38
  };
@@ -44,6 +49,45 @@ function normalizeSessionKey(sessionId: string): string {
44
49
  return normalized || 'default';
45
50
  }
46
51
 
52
+ function envFlagEnabled(name: string, defaultValue: boolean): boolean {
53
+ const raw = process.env[name];
54
+ if (raw == null || raw.trim() === '') return defaultValue;
55
+ return !ENV_FALSEY.has(raw.trim().toLowerCase());
56
+ }
57
+
58
+ function deriveStableId(raw: string, maxLength = 40): string {
59
+ const base =
60
+ String(raw || 'default')
61
+ .toLowerCase()
62
+ .replace(/[^a-z0-9_-]+/g, '_')
63
+ .replace(/^_+|_+$/g, '') || 'default';
64
+ const hash = createHash('sha256').update(raw).digest('hex').slice(0, 10);
65
+ const headLength = Math.max(1, maxLength - hash.length - 1);
66
+ return `${base.slice(0, headLength)}_${hash}`;
67
+ }
68
+
69
+ function shouldPersistProfiles(): boolean {
70
+ return envFlagEnabled('BROWSER_PERSIST_PROFILE', true);
71
+ }
72
+
73
+ function shouldPersistSessionState(): boolean {
74
+ return envFlagEnabled('BROWSER_PERSIST_SESSION_STATE', true);
75
+ }
76
+
77
+ function resolveProfileRoot(): string {
78
+ const configured = String(process.env.BROWSER_PROFILE_ROOT || '').trim();
79
+ if (!configured) return ensureWritableDir(BROWSER_PROFILE_ROOT);
80
+ const resolved = path.isAbsolute(configured) ? configured : path.resolve(WORKSPACE_ROOT, configured);
81
+ return ensureWritableDir(resolved);
82
+ }
83
+
84
+ function resolveCdpUrl(explicit?: string): string | undefined {
85
+ const direct = String(explicit || '').trim();
86
+ if (direct) return direct;
87
+ const configured = String(process.env.BROWSER_CDP_URL || '').trim();
88
+ return configured || undefined;
89
+ }
90
+
47
91
  function resolveRunner(): BrowserRunner | null {
48
92
  if (cachedRunner !== undefined) {
49
93
  return cachedRunner;
@@ -86,12 +130,27 @@ function getSession(sessionId: string): BrowserSession {
86
130
  }
87
131
 
88
132
  fs.mkdirSync(BROWSER_SOCKET_ROOT, { recursive: true, mode: 0o700 });
89
- const socketDir = path.join(BROWSER_SOCKET_ROOT, sessionKey);
133
+ const runtimeKey = deriveStableId(sessionKey, 32);
134
+ const socketDir = path.join(BROWSER_SOCKET_ROOT, runtimeKey);
90
135
  fs.mkdirSync(socketDir, { recursive: true, mode: 0o700 });
91
136
 
137
+ let profileDir: string | undefined;
138
+ if (shouldPersistProfiles()) {
139
+ try {
140
+ profileDir = ensureWritableDir(path.join(resolveProfileRoot(), runtimeKey));
141
+ } catch {
142
+ // Fallback to ephemeral browser context if profile dir cannot be created.
143
+ profileDir = undefined;
144
+ }
145
+ }
146
+
147
+ const stateName = shouldPersistSessionState() ? deriveStableId(sessionKey, 48) : undefined;
148
+
92
149
  const session: BrowserSession = {
93
150
  sessionKey,
94
151
  socketDir,
152
+ profileDir,
153
+ stateName,
95
154
  createdAt: Date.now(),
96
155
  lastUsedAt: Date.now(),
97
156
  };
@@ -290,24 +349,34 @@ async function runAgentBrowser(
290
349
  const xdgCacheDir = ensureWritableDir(BROWSER_XDG_CACHE);
291
350
  const playwrightBrowsersPath = resolvePlaywrightBrowsersPath();
292
351
  const args = [...runner.prefixArgs];
293
- if (options.cdpUrl && options.cdpUrl.trim()) {
294
- args.push('--cdp', options.cdpUrl.trim());
352
+ const cdpUrl = resolveCdpUrl(options.cdpUrl);
353
+ if (cdpUrl) {
354
+ args.push('--cdp', cdpUrl);
295
355
  }
296
356
  args.push('--json', command, ...commandArgs);
297
357
 
358
+ const browserEnv: NodeJS.ProcessEnv = {
359
+ ...process.env,
360
+ AGENT_BROWSER_SOCKET_DIR: session.socketDir,
361
+ AGENT_BROWSER_SESSION: 'default',
362
+ HOME: homeDir,
363
+ XDG_CACHE_HOME: xdgCacheDir,
364
+ NPM_CONFIG_CACHE: npmCacheDir,
365
+ npm_config_cache: npmCacheDir,
366
+ PLAYWRIGHT_BROWSERS_PATH: playwrightBrowsersPath,
367
+ };
368
+ if (session.stateName) {
369
+ browserEnv.AGENT_BROWSER_SESSION_NAME = session.stateName;
370
+ }
371
+ if (!cdpUrl && session.profileDir) {
372
+ browserEnv.AGENT_BROWSER_PROFILE = session.profileDir;
373
+ }
374
+
298
375
  try {
299
376
  const { stdout, stderr } = await execFileAsync(runner.cmd, args, {
300
377
  timeout: timeoutMs,
301
378
  maxBuffer: 2 * 1024 * 1024,
302
- env: {
303
- ...process.env,
304
- AGENT_BROWSER_SOCKET_DIR: session.socketDir,
305
- HOME: homeDir,
306
- XDG_CACHE_HOME: xdgCacheDir,
307
- NPM_CONFIG_CACHE: npmCacheDir,
308
- npm_config_cache: npmCacheDir,
309
- PLAYWRIGHT_BROWSERS_PATH: playwrightBrowsersPath,
310
- },
379
+ env: browserEnv,
311
380
  });
312
381
 
313
382
  const output = String(stdout || '').trim();
package/docs/index.html CHANGED
@@ -1010,7 +1010,7 @@
1010
1010
  </div>
1011
1011
  <div class="feature-card">
1012
1012
  <h3 style="color: var(--accent-1);">Safety Model</h3>
1013
- <p>Trust-model acceptance is required during onboarding, execution uses runtime guardrails by default, and TUI blocks on unapproved instruction changes.</p>
1013
+ <p>Trust-model acceptance is required during onboarding, execution uses runtime guardrails by default, user-directed browser auth-flow testing is supported, and TUI blocks on unapproved instruction changes.</p>
1014
1014
  </div>
1015
1015
  <div class="feature-card">
1016
1016
  <h3 style="color: var(--accent-1);">Prompt Orchestration</h3>
@@ -1102,7 +1102,7 @@
1102
1102
  <div class="tool-pill">
1103
1103
  <div class="tool-pill-icon">&#x1f5a5;&#xfe0f;</div>
1104
1104
  <div class="tool-pill-name">browser_*</div>
1105
- <div class="tool-pill-desc">Navigate, snapshot, click, type, screenshot, PDF</div>
1105
+ <div class="tool-pill-desc">Navigate, snapshot, click, type, auth-flow testing, screenshot, PDF</div>
1106
1106
  </div>
1107
1107
  <div class="tool-pill">
1108
1108
  <div class="tool-pill-icon">&#x1f551;</div>
@@ -1201,6 +1201,10 @@
1201
1201
  <div class="faq-q">Is it safe to let the agent run shell commands?</div>
1202
1202
  <div class="faq-a">Yes. All tools execute inside ephemeral Docker containers with read-only filesystems, memory caps, and a deny-list of 28+ dangerous command patterns. The host machine is never exposed.</div>
1203
1203
  </div>
1204
+ <div class="faq-item">
1205
+ <div class="faq-q">Can browser tools test real login flows?</div>
1206
+ <div class="faq-a">Yes, when explicitly requested by the user for the intended site. Runtime guidance allows authenticated browser-flow testing, while sensitive credential values are redacted from structured audit tool-argument logs.</div>
1207
+ </div>
1204
1208
  <div class="faq-item">
1205
1209
  <div class="faq-q">Is the audit trail immutable?</div>
1206
1210
  <div class="faq-a">Audit logs are append-only and hash-chained per session, so modifications are tamper-evident. Use <code>hybridclaw audit verify &lt;sessionId&gt;</code> to validate integrity, and <code>hybridclaw audit approvals --denied</code> to review denied actions.</div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hybridaione/hybridclaw",
3
- "version": "0.1.19",
3
+ "version": "0.1.20",
4
4
  "type": "module",
5
5
  "description": "Personal AI assistant bot for Discord, powered by HybridAI",
6
6
  "publishConfig": {
@@ -35,6 +35,31 @@ function summarizeToolResult(text: string): string {
35
35
  return truncateAuditText(text, 280);
36
36
  }
37
37
 
38
+ const SENSITIVE_ARG_KEY_RE = /(pass(word)?|secret|token|api[_-]?key|authorization|cookie|credential|session)/i;
39
+
40
+ function sanitizeAuditArguments(toolName: string, value: unknown): unknown {
41
+ if (Array.isArray(value)) {
42
+ return value.map((entry) => sanitizeAuditArguments(toolName, entry));
43
+ }
44
+ if (!value || typeof value !== 'object') {
45
+ return value;
46
+ }
47
+
48
+ const out: Record<string, unknown> = {};
49
+ for (const [key, raw] of Object.entries(value as Record<string, unknown>)) {
50
+ if (SENSITIVE_ARG_KEY_RE.test(key)) {
51
+ out[key] = '[REDACTED]';
52
+ continue;
53
+ }
54
+ if (toolName === 'browser_type' && key === 'text') {
55
+ out[key] = '[REDACTED]';
56
+ continue;
57
+ }
58
+ out[key] = sanitizeAuditArguments(toolName, raw);
59
+ }
60
+ return out;
61
+ }
62
+
38
63
  export function emitToolExecutionAuditEvents(input: {
39
64
  sessionId: string;
40
65
  runId: string;
@@ -44,6 +69,7 @@ export function emitToolExecutionAuditEvents(input: {
44
69
  toolExecutions.forEach((execution, index) => {
45
70
  const toolCallId = `${runId}:tool:${index + 1}`;
46
71
  const argumentsObject = parseJsonObject(execution.arguments || '{}');
72
+ const auditArguments = sanitizeAuditArguments(execution.name, argumentsObject);
47
73
 
48
74
  recordAuditEvent({
49
75
  sessionId,
@@ -52,7 +78,7 @@ export function emitToolExecutionAuditEvents(input: {
52
78
  type: 'tool.call',
53
79
  toolCallId,
54
80
  toolName: execution.name,
55
- arguments: argumentsObject,
81
+ arguments: auditArguments,
56
82
  },
57
83
  });
58
84
 
@@ -71,6 +71,12 @@ function buildSafetyHook(context: PromptHookContext): string {
71
71
  'Use bash for execution/build/validation tasks, not for file authoring.',
72
72
  'After file changes, run commands only when asked; otherwise explicitly offer to run them immediately.',
73
73
  'Only skip file creation when the user explicitly asks for snippet-only or explanation-only output.',
74
+ '',
75
+ '## Browser Auth Handling',
76
+ 'When the user explicitly asks for login/auth-flow testing, browser tools may be used on the requested site, including filling credentials and submitting forms.',
77
+ 'Do not invent blanket restrictions such as "browser tools are only for public/unauthenticated pages" unless an actual tool/policy error says so.',
78
+ 'If earlier assistant messages claimed stricter login limits, treat those as stale and follow this policy and real tool outcomes.',
79
+ 'Use provided credentials only for the requested auth flow; do not echo them in prose, write them to files, or send them to unrelated domains.',
74
80
  ];
75
81
 
76
82
  if (accepted) {