Back to skill

Security audit

gmail-wiki-ingest

Security checks for vulnerabilities and agentic risk

Overview

The skill’s Gmail-to-wiki behavior is mostly disclosed and coherent, but it should be reviewed because it handles private email data with a shared bearer token and an unvalidated configurable server endpoint.

Review this before installing if you do not specifically want HiJavis to read recent Gmail metadata daily and sometimes read selected message bodies for wiki triage. Operators should lock JAVIS_SERVER_URL to the intended javis-server origin, prefer scoped credentials, and ensure the local data directory is private. Users should understand that trusted senders can be auto-ingested and should use the app switch or discard/undo controls if the behavior is not wanted.

Vulnerability Patterns
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • 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
Findings (2)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/gmail-wiki-ingest.js:47
Finding
Redirectable server endpoint can disclose the shared gateway bearer token and private user data<![CDATA[ ## Vulnerability Details **File Location**: `scripts/gmail-wiki-ingest.js:47-48, 100-112` **Vulnerability Type**: Unvalidated network destination for authenticated requests **Risk Level**: High ### Vulnerable Code ```js const SERVER = process.env.JAVIS_SERVER_URL || 'http://javis-server:8000'; const SKILL = 'gmail-wiki-ingest'; ``` ```js async function postJson(path, body, deps = {}) { const fetchFn = deps.fetch || globalThis.fetch; const token = deps.token || requireToken(); let res; try { res = await fetchFn(`${SERVER}${path}`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}`, }, body: JSON.stringify(body), }); ``` ### Technical Analysis The destination of every authenticated request is derived from the environment-controlled `JAVIS_SERVER_URL`. The code performs no validation of its scheme, hostname, port, or origin before attaching `OPENCLAW_GATEWAY_TOKEN` as a bearer credential. Consequently, a party able to influence the Skill's environment can direct `fetch`, `content`, `submit`, or `report` requests to an attacker-controlled server. The attacker would receive both the bearer token and the associated request body. Depending on the command, that body may include selected Gmail thread identifiers, email-derived verdicts and reasons, or report content. The default destination also uses plaintext HTTP. While `javis-server` appears intended to be an internal service, the code does not verify that the destination remains local or trusted. If traffic crosses an untrusted network segment, credentials and private data could be exposed to interception. This exceeds strict least privilege because the credential is documented as a container-wide token shared by installed skills rather than a credential scoped solely to Gmail ingestion. ### Attack Path 1. An attacker gains control over the process or container configuration sufficiently to set ` ...[truncated 1340 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove `JAVIS_SERVER_URL` configurability from production builds where it is unnecessary. 2. If configuration is required, parse the URL with `new URL()` and enforce an exact allowlist of trusted origins, including the expected scheme, hostname, and port. 3. Require HTTPS for non-loopback and non-verified internal destinations. Reject arbitrary plaintext HTTP endpoints. 4. Prefer a per-Skill credential restricted to the exact Gmail-ingest endpoints instead of a container-wide user credential. 5. Configure requests to reject redirects, or independently validate every redirect destination before allowing credentials to be forwarded. 6. Separate authorization scopes for candidate retrieval, verdict submission, and chat reporting where supported. 7. Add tests proving that unapproved hosts, user-info URLs, unexpected ports, and insecure schemes are rejected before the token is read or attached. 8. Avoid including sensitive response details or credentials in network-error logs. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/gmail-wiki-ingest.js:188
Finding
Email headers and thread identifiers are persisted without explicit restrictive filesystem permissions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/gmail-wiki-ingest.js:188-193, 216-235, 598-608` **Vulnerability Type**: Insecure local storage of sensitive email metadata **Risk Level**: Medium ### Vulnerable Code ```js function writeState(state, deps = {}) { const file = deps.statePath || STATE_PATH; fs.mkdirSync(path.dirname(file), { recursive: true }); const tmp = `${file}.tmp`; fs.writeFileSync(tmp, JSON.stringify(state, null, 2)); fs.renameSync(tmp, file); } ``` The persisted state includes private mail metadata: ```js async function doFetch(opts = {}, deps = {}) { const limit = Number(opts.limit) || 25; const out = await postJson('/api/skill/candidates/fetch', { skill: SKILL, limit }, deps); if (!failed(out) && out.status === 'ok') { const items = Array.isArray(out.items) ? out.items : []; saveState({ started_at: nowIso(deps), n_items: items.length, filtered: (out.filtered && typeof out.filtered === 'object') ? out.filtered : {}, items: items.map((it) => ({ thread_id: it && it.thread_id, subject: it && it.subject, from: it && it.from, })), }, deps); } return out; } ``` State is deleted only after a successful report push: ```js const content = renderReport(state, input); const pushed = await postJson('/api/agent/push', { skill: SKILL, content }, deps); if (failed(pushed)) return pushed; deleteState(deps); return { status: 'ok', content }; } ``` ### Technical Analysis The Skill deliberately avoids persisting full message bodies, which substantially reduces exposure. However, it writes Gmail thread identifiers, subjects, and sender fields to `data/last-run.json` through a temporary sibling file without explicitly setting restrictive directory or file modes. Actual accessibility therefore depends on the process umask, parent-directory permissions, runtime user isolation, and container configuration. Under a permissive umask, the state file ...[truncated 1717 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Create the state directory with mode `0700`: ```js fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 }); ``` 2. Write the temporary state file with mode `0600` and an exclusive or safely replacing creation strategy: ```js fs.writeFileSync(tmp, JSON.stringify(state, null, 2), { mode: 0o600 }); ``` 3. Explicitly verify and correct existing directory and file permissions because `mode` does not tighten permissions on an already-existing directory. 4. Verify that the state file is owned by the dedicated runtime account and that the containing volume is not shared with unrelated workloads. 5. Delete stale state when `doReport` detects that it is older than the accepted six-hour window, where doing so does not interfere with operational recovery. 6. Add startup cleanup for abandoned temporary files and expired `last-run.json` files. 7. Consider storing opaque local item indexes instead of sender and subject fields if the server can safely provide report facts again. 8. Add tests that assert restrictive permissions and stale-state deletion on supported POSIX platforms. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (22)

Ae1

High
Category
analysis-evasion
Content
node scripts/gmail-wiki-ingest.js fetch ──► metadata + knowledge model
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/gmail-wiki-ingest.js fetch ──► metadata + knowledge model
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/gmail-wiki-ingest.js fetch ──► metadata + knowledge model
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/gmail-wiki-ingest.js fetch ──► metadata + knowledge model
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/gmail-wiki-ingest.js fetch ──► metadata + knowledge model
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/gmail-wiki-ingest.js fetch ──► metadata + knowledge model
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/gmail-wiki-ingest.js fetch ──► metadata + knowledge model
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/gmail-wiki-ingest.js fetch ──► metadata + knowledge model
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/gmail-wiki-ingest.js fetch ──► metadata + knowledge model
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/gmail-wiki-ingest.js fetch ──► metadata + knowledge model
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Vague Triggers

Medium
Confidence
93% confidence
Finding
The listed invocation phrases include broad natural-language requests such as "ingest my email" and "sync my inbox to the wiki," which could plausibly occur in ordinary conversation with the assistant outside an explicit command context. The README does not provide negative examples or contextual limits for when these phrases should or should not activate the skill.

Lp3

Medium
Category
MCP Least Privilege
Confidence
84% confidence
Finding
The skill explicitly relies on environment access and outbound network calls via a gateway token, yet declares no tool/permission scope in metadata. That weakens least-privilege controls and makes the skill harder for a platform to govern or review, especially because it processes mailbox data and can write results back to a server.

Vague Triggers

Medium
Confidence
85% confidence
Finding
The trigger phrase "ingest my email" is broad and can plausibly match ordinary user requests about handling email rather than intentionally launching a mailbox-wide ingest job. Because this skill reads mailbox metadata and sometimes message bodies, accidental activation could cause unintended access to sensitive email content.

Vague Triggers

Medium
Confidence
91% confidence
Finding
The trigger phrase 'ingest my email' is broad and closely matches ordinary user language, so the skill could be invoked unintentionally during normal conversation. Because this skill processes privacy-sensitive inbox data and can run on demand, accidental activation increases the risk of unexpected email access and downstream submission of triage results.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The manifest states that email content and verdicts are transmitted to 'javis-server' with a gateway token, but it does not indicate any explicit user-facing privacy notice or consent checkpoint for handling sensitive email data. Even if writes and validation stay server-side, the skill still accesses and transmits private content, so lack of transparent warning can lead to privacy violations or user surprise.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
|---|---|
| **LOW** | no card, no page. An auto-discard row on the ledger, `source='auto'`. |
| **MIDDLE** | a pending `SkillData` row — the review card the user answers with Confirm / Discard. This is the day-one behavior and still the common case. |
| **HIGH** | auto-confirmed and distilled into the wiki on the spot, with an undo offered on the card for a bounded window. |

**The score alone never reaches HIGH.** The measured bands overlap — true-keep
mail scores as low as 0.70, unwanted mail as high as 0.80 — so no cut point
Confidence
85% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
|---|---|
| **LOW** | no card, no page. An auto-discard row on the ledger, `source='auto'`. |
| **MIDDLE** | a pending `SkillData` row — the review card the user answers with Confirm / Discard. This is the day-one behavior and still the common case. |
| **HIGH** | auto-confirmed and distilled into the wiki on the spot, with an undo offered on the card for a bounded window. |

**The score alone never reaches HIGH.** The measured bands overlap — true-keep
mail scores as low as 0.70, unwanted mail as high as 0.80 — so no cut point
Confidence
85% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
|---|---|
| **LOW** | no card, no page. An auto-discard row on the ledger, `source='auto'`. |
| **MIDDLE** | a pending `SkillData` row — the review card the user answers with Confirm / Discard. This is the day-one behavior and still the common case. |
| **HIGH** | auto-confirmed and distilled into the wiki on the spot, with an undo offered on the card for a bounded window. |

**The score alone never reaches HIGH.** The measured bands overlap — true-keep
mail scores as low as 0.70, unwanted mail as high as 0.80 — so no cut point
Confidence
85% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
|---|---|
| **LOW** | no card, no page. An auto-discard row on the ledger, `source='auto'`. |
| **MIDDLE** | a pending `SkillData` row — the review card the user answers with Confirm / Discard. This is the day-one behavior and still the common case. |
| **HIGH** | auto-confirmed and distilled into the wiki on the spot, with an undo offered on the card for a bounded window. |

**The score alone never reaches HIGH.** The measured bands overlap — true-keep
mail scores as low as 0.70, unwanted mail as high as 0.80 — so no cut point
Confidence
85% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Missing User Warnings

Medium
Confidence
80% confidence
Finding
The `content` command sends requested email body data to `/api/skill/candidates/content`, which is a privacy-relevant network operation involving mailbox content. Although the file comments explain the behavior for developers, there is no user-facing confirmation, prompt, or runtime disclosure at the point of execution in this code file.

Natural-Language Policy Violations

Low
Confidence
71% confidence
Finding
The file hard-codes specific non-English trigger phrases as activation inputs, but does not explain whether multilingual triggering is user-configurable or limited to users who opted in. This can be a language/locale policy concern when locale behavior is prescribed without documented user choice.

Missing User Warnings

Low
Confidence
70% confidence
Finding
The `report` command transmits a generated digest containing mailbox-derived metadata and model-authored notes to `/api/agent/push`. This is a network action affecting user data visibility, but the code does not provide a user-facing notice, confirmation, or similar disclosure when the push occurs.

Static analysis

Detected: suspicious.dangerous_exec

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
test/cli.test.js:315