Back to skill

Security audit

camofox-vault-login

Security checks for vulnerabilities and agentic risk

Overview

This skill uses explicitly configured vault credentials for website login filling, with disclosed safeguards and browser-security limits.

Trust the installed Bitwarden and Camofox components, configure the intended vault identity, and review browser tracing/logging before use. The helper can read vault contents internally, and websites receive filled credentials. Follow dry-run review and never automatically retry uncertain fills. Some bundled tests failed here; syntax and targeted synthetic checks passed.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (19)

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · references/setup.md (reported line 11)May include surrounding context.

md
Supported string keys (unknown keys rejected):
- expectedVaultServerUrl, expectedVaultEmail: required; exact comparison with bw status, before unlock or item retrieval. No automatic account trust or account/server switching.
- keychainService, keychainAccount: optional paired keys for an existing macOS generic password. No Keychain writes. Do not configure on other OSes.
- camofoxRuntimePath: optional absolute trusted installed package root; defaults to OPENCLAW_STATE_DIR/extensions/camofox-browser, or ~/.openclaw/extensions/camofox-browser.
- openclawConfigPath: optional absolute existing OpenClaw config; defaults to OPENCLAW_STATE_DIR/openclaw.json, or ~/.openclaw/openclaw.json.

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/bridge.test.mjs (reported line 7)May include surrounding context.

js
import { createVault, childEnv, captured, inspectionExpression } from './runtime.mjs';
const id = '11111111-1111-1111-1111-111111111111';
const secret = 'SYNTHETIC_PASSWORD_MARKER';
const profile = { expectedVaultServerUrl: 'https://vault.example.invalid', expectedVaultEmail: 'fixture@example.invalid', keychainAccount: 'fixture', keychainService: 'fixture-vault' };
const site = 'https://login.example.invalid';
const item = () => ({ id, type: 1, name: secret, login: { username: 'SYNTHETIC_USER_MARKER', password: secret, totp: 'JBSWY3DPEHPK3PXP', uris: [{ uri: site, match: null }] } });
function fixture() {

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/bridge.test.mjs (reported line 7)May include surrounding context.

js
import { createVault, childEnv, captured, inspectionExpression } from './runtime.mjs';
const id = '11111111-1111-1111-1111-111111111111';
const secret = 'SYNTHETIC_PASSWORD_MARKER';
const profile = { expectedVaultServerUrl: 'https://vault.example.invalid', expectedVaultEmail: 'fixture@example.invalid', keychainAccount: 'fixture', keychainService: 'fixture-vault' };
const site = 'https://login.example.invalid';
const item = () => ({ id, type: 1, name: secret, login: { username: 'SYNTHETIC_USER_MARKER', password: secret, totp: 'JBSWY3DPEHPK3PXP', uris: [{ uri: site, match: null }] } });
function fixture() {

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/bridge.test.mjs (reported line 36)May include surrounding context.

js
; no secret args', async () => { const calls = []; const v = createVault({ profile, env: { PATH: '/bin', BW_SESSION: 'SESSION_MARKER', OTHER_SECRET: secret }, run: async (file, args, env) => { calls.push({ file, args, env }); return args[0] === 'status' ? '{}' : '[]'; } }); await v.status(); await v.items(); assert.equal(calls[0].env.BW_SESSION, 'SESSION_MARKER'); assert.equal(calls[0].env.OTHER_SECRET, undefined); assert.ok(!JSON.stringify(calls.map(c => c.args)).includes('MARKER')); });
test('keychain password only in unlock env; session only bw env', async () => { const calls = []; const v = createVault({ profile, env: { BW_SESSION: 'OLD_MARKER' }, run: async (file,args,env) => { calls.push({file,args,env}); return file.includes('security') ? 'MASTER_MARKER\n' : args[0] === 'unlock' ? 'SESSION_MARKER' : '[]'; } }); await v.unlock(); await v.items(); assert.equal(calls[0].env.BW_SESSION, undefined); assert.equal(calls[1].env.BW_PASSWORD, 'MASTER_MARKER'); assert.equal(calls[1].env.BW

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/packaging.test.mjs (reported line 41)May include surrounding context.

js
const explicit=await loadProfile({env,read:async()=>JSON.stringify({...profile,camofoxRuntimePath:'/runtime',openclawConfigPath:'/config'})});assert.equal(explicit.runtimePath,'/runtime');assert.equal(explicit.configPath,'/config');
});
test('profile validation rejects unsupported or secret keys and relative paths',async()=>{
  for(const patch of [{password:'PRIVATE_MARKER'},{keychainAccount:'fixture'},{expectedVaultServerUrl:'http://vault.example.invalid'},{camofoxRuntimePath:'relative'},{expectedVaultEmail:42},{expectedVaultEmail:'bad\nvalue'}]) await assert.rejects(loadProfile({env,read:async()=>JSON.stringify({...profile,...patch})}));
  await assert.rejects(loadProfile({env:{CAMOFOX_VAULT_CONFIG:'relative'}}),{code:'PROFILE_PATH_REQUIRED'});
  for(const data of ['null','[]','"PRIVATE_MARKER"','invalid'])await assert.rejects(loadProfile({env,read:async()=>data}));
});

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/runtime.mjs (reported line 121)May include surrounding context.

js
let profile;
  try { profile = JSON.parse(await read(path, 'utf8')); } catch { stop('PROFILE_UNAVAILABLE'); }
  if (!profile || typeof profile !== 'object' || Array.isArray(profile)) stop('PROFILE_INVALID');
  const allowed = ['expectedVaultServerUrl','expectedVaultEmail','keychainService','keychainAccount','camofoxRuntimePath','openclawConfigPath'];
  if (Object.keys(profile).some(k => !allowed.includes(k)) || Object.values(profile).some(v => typeof v !== 'string' || !v || /[\r\n\0]/.test(v))) stop('PROFILE_INVALID');
  if (!profile.expectedVaultServerUrl || !profile.expectedVaultEmail) stop('VAULT_IDENTITY_REQUIRED');
  let u; try { u = new URL(profile.expectedVaultServerUrl); } catch { stop('PROFILE_INVALID'); }

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/runtime.mjs (reported line 121)May include surrounding context.

js
let profile;
  try { profile = JSON.parse(await read(path, 'utf8')); } catch { stop('PROFILE_UNAVAILABLE'); }
  if (!profile || typeof profile !== 'object' || Array.isArray(profile)) stop('PROFILE_INVALID');
  const allowed = ['expectedVaultServerUrl','expectedVaultEmail','keychainService','keychainAccount','camofoxRuntimePath','openclawConfigPath'];
  if (Object.keys(profile).some(k => !allowed.includes(k)) || Object.values(profile).some(v => typeof v !== 'string' || !v || /[\r\n\0]/.test(v))) stop('PROFILE_INVALID');
  if (!profile.expectedVaultServerUrl || !profile.expectedVaultEmail) stop('VAULT_IDENTITY_REQUIRED');
  let u; try { u = new URL(profile.expectedVaultServerUrl); } catch { stop('PROFILE_INVALID'); }

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/runtime.mjs (reported line 126)May include surrounding context.

js
if (!profile.expectedVaultServerUrl || !profile.expectedVaultEmail) stop('VAULT_IDENTITY_REQUIRED');
  let u; try { u = new URL(profile.expectedVaultServerUrl); } catch { stop('PROFILE_INVALID'); }
  if (u.protocol !== 'https:' || u.username || u.password || u.search || u.hash || u.pathname !== '/') stop('PROFILE_INVALID');
  if (!!profile.keychainAccount !== !!profile.keychainService) stop('PROFILE_INVALID');
  const stateDir = env.OPENCLAW_STATE_DIR || (home && join(home, '.openclaw'));
  const runtimePath = profile.camofoxRuntimePath || (stateDir && join(stateDir, 'extensions', 'camofox-browser'));
  const configPath = profile.openclawConfigPath || (stateDir && join(stateDir, 'openclaw.json'));

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/runtime.mjs (reported line 126)May include surrounding context.

js
if (!profile.expectedVaultServerUrl || !profile.expectedVaultEmail) stop('VAULT_IDENTITY_REQUIRED');
  let u; try { u = new URL(profile.expectedVaultServerUrl); } catch { stop('PROFILE_INVALID'); }
  if (u.protocol !== 'https:' || u.username || u.password || u.search || u.hash || u.pathname !== '/') stop('PROFILE_INVALID');
  if (!!profile.keychainAccount !== !!profile.keychainService) stop('PROFILE_INVALID');
  const stateDir = env.OPENCLAW_STATE_DIR || (home && join(home, '.openclaw'));
  const runtimePath = profile.camofoxRuntimePath || (stateDir && join(stateDir, 'extensions', 'camofox-browser'));
  const configPath = profile.openclawConfigPath || (stateDir && join(stateDir, 'openclaw.json'));

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/bridge.test.mjs (reported line 67)May include surrounding context.

js
});

// Review regressions: use real child-process stdout bytes, not a mock backslash-n.
test('actual subprocess newline trimmed from Keychain output', async () => {
  for (const ending of ['LF','CRLF']) {
    const v = createVault({ profile, env: {}, run: async (file,args,env) => {
      if (file.includes('security')) return captured(process.execPath, ['-e', 'process.stdout.write("MASTER_MARKER" + String.fromCharCode(' + (ending === 'LF' ? '10' : '13,10') + '))'], childEnv());

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/runtime.mjs (reported line 7)May include surrounding context.

js
import { resolve, isAbsolute, join } from 'node:path';
import { BridgeError } from './bridge.mjs';
const stop = code => { throw new BridgeError(code); };
// Strict allowlist: unrelated credentials, NODE_OPTIONS, and BW_SESSION never reach Keychain.
export function childEnv(env = process.env) {
  return Object.fromEntries(['PATH','HOME','USER','TMPDIR','LANG','SSL_CERT_FILE','NODE_EXTRA_CA_CERTS','BITWARDENCLI_APPDATA_DIR'].filter(k => env[k]).map(k => [k, env[k]]));
}

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/packaging.test.mjs (reported line 47)May include surrounding context.

js
});
test('locked vault without configured provider fails clearly on any OS',async()=>{
  for(const platform of ['linux','darwin','win32'])await assert.rejects(createVault({platform,profile,run:async()=>{throw Error('must not call');}}).unlock(),{code:'UNLOCK_PROVIDER_MISSING'});
  await assert.rejects(createVault({platform:'linux',profile:{...profile,keychainService:'fixture',keychainAccount:'fixture'}}).unlock(),{code:'UNLOCK_PROVIDER_MISSING'});
});
test('BW_SESSION reuse requires no unlock provider on non-Mac',async()=>{
  const v=createVault({platform:'linux',profile,env:{BW_SESSION:'SESSION_MARKER'},run:async(file,args,env)=>{assert.equal(env.BW_SESSION,'SESSION_MARKER');assert.equal(args[0],'status');return '{}';}});await v.status();

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/packaging.test.mjs (reported line 47)May include surrounding context.

js
});
test('locked vault without configured provider fails clearly on any OS',async()=>{
  for(const platform of ['linux','darwin','win32'])await assert.rejects(createVault({platform,profile,run:async()=>{throw Error('must not call');}}).unlock(),{code:'UNLOCK_PROVIDER_MISSING'});
  await assert.rejects(createVault({platform:'linux',profile:{...profile,keychainService:'fixture',keychainAccount:'fixture'}}).unlock(),{code:'UNLOCK_PROVIDER_MISSING'});
});
test('BW_SESSION reuse requires no unlock provider on non-Mac',async()=>{
  const v=createVault({platform:'linux',profile,env:{BW_SESSION:'SESSION_MARKER'},run:async(file,args,env)=>{assert.equal(env.BW_SESSION,'SESSION_MARKER');assert.equal(args[0],'status');return '{}';}});await v.status();

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/runtime.mjs (reported line 20)May include surrounding context.

js
return {
    status: async () => JSON.parse(await bw(['status'])),
    unlock: async () => {
      if (platform !== 'darwin' || !profile.keychainAccount || !profile.keychainService) stop('UNLOCK_PROVIDER_MISSING');
      const password = (await run('/usr/bin/security', ['find-generic-password','-a',profile.keychainAccount,'-s',profile.keychainService,'-w'], base)).replace(/\r?\n$/, '');
      if (!password) stop('KEYCHAIN_EMPTY');
      session = (await run('bw', ['unlock','--raw','--passwordenv','BW_PASSWORD'], { ...base, BW_PASSWORD: password })).trim();

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/runtime.mjs (reported line 20)May include surrounding context.

js
return {
    status: async () => JSON.parse(await bw(['status'])),
    unlock: async () => {
      if (platform !== 'darwin' || !profile.keychainAccount || !profile.keychainService) stop('UNLOCK_PROVIDER_MISSING');
      const password = (await run('/usr/bin/security', ['find-generic-password','-a',profile.keychainAccount,'-s',profile.keychainService,'-w'], base)).replace(/\r?\n$/, '');
      if (!password) stop('KEYCHAIN_EMPTY');
      session = (await run('bw', ['unlock','--raw','--passwordenv','BW_PASSWORD'], { ...base, BW_PASSWORD: password })).trim();

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/runtime.mjs (reported line 21)May include surrounding context.

js
status: async () => JSON.parse(await bw(['status'])),
    unlock: async () => {
      if (platform !== 'darwin' || !profile.keychainAccount || !profile.keychainService) stop('UNLOCK_PROVIDER_MISSING');
      const password = (await run('/usr/bin/security', ['find-generic-password','-a',profile.keychainAccount,'-s',profile.keychainService,'-w'], base)).replace(/\r?\n$/, '');
      if (!password) stop('KEYCHAIN_EMPTY');
      session = (await run('bw', ['unlock','--raw','--passwordenv','BW_PASSWORD'], { ...base, BW_PASSWORD: password })).trim();
      if (!session) stop('UNLOCK_FAILED');

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/runtime.mjs (reported line 21)May include surrounding context.

js
status: async () => JSON.parse(await bw(['status'])),
    unlock: async () => {
      if (platform !== 'darwin' || !profile.keychainAccount || !profile.keychainService) stop('UNLOCK_PROVIDER_MISSING');
      const password = (await run('/usr/bin/security', ['find-generic-password','-a',profile.keychainAccount,'-s',profile.keychainService,'-w'], base)).replace(/\r?\n$/, '');
      if (!password) stop('KEYCHAIN_EMPTY');
      session = (await run('bw', ['unlock','--raw','--passwordenv','BW_PASSWORD'], { ...base, BW_PASSWORD: password })).trim();
      if (!session) stop('UNLOCK_FAILED');

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/runtime.mjs (reported line 22)May include surrounding context.

js
unlock: async () => {
      if (platform !== 'darwin' || !profile.keychainAccount || !profile.keychainService) stop('UNLOCK_PROVIDER_MISSING');
      const password = (await run('/usr/bin/security', ['find-generic-password','-a',profile.keychainAccount,'-s',profile.keychainService,'-w'], base)).replace(/\r?\n$/, '');
      if (!password) stop('KEYCHAIN_EMPTY');
      session = (await run('bw', ['unlock','--raw','--passwordenv','BW_PASSWORD'], { ...base, BW_PASSWORD: password })).trim();
      if (!session) stop('UNLOCK_FAILED');
    },

Skill declares no tool scope ('permissions' or 'allowed-tools') but code capabilities were detected: env, shell.

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding

Without declared permissions the skill's intent is opaque and cannot be validated.

Content

No source excerpt is available for this finding.

Static analysis

Detected: suspicious.dangerous_exec

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
scripts/bridge.test.mjs:77

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
scripts/packaging.test.mjs:15

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
scripts/runtime.mjs:12