Back to skill

Security audit

Briefed

Security checks for vulnerabilities and agentic risk

Overview

Briefed is a coherent newsletter reader, but it handles Gmail data with persistent automation and an unauthenticated local web API that may expose private email content if the port is reachable.

Review this skill before installing. Use it only if you are comfortable granting read-only Gmail access, storing email-derived data and a reusable OAuth token locally, and sending newsletter content to your configured model provider. Prefer manual runs first, avoid enabling the LaunchAgent or cron until you need them, bind the reader to 127.0.0.1 with authentication if possible, do not expose port 3001, and restrict permissions on the Gmail token file.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • System PersistenceInstalls backdoors, hooks, services, or scheduled tasks that survive the run
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (5)

T06 · System Persistence

Error
Location
SKILL.md:127
Finding
Persistent LaunchAgent and Daily Scheduled Agent Execution<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:127-155`, `SKILL.md:158-202` **Vulnerability Type**: Cross-session service and scheduled-task persistence **Risk Level**: High ### Vulnerable Code ```xml LaunchAgent plist template: <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"><dict> <key>Label</key><string>ai.openclaw.briefed</string> <key>ProgramArguments</key><array> <string>/usr/local/bin/node</string> <string>/Users/YOUR_USER/.openclaw/workspace/briefed/server.js</string> </array> <key>EnvironmentVariables</key><dict> <key>BRIEFED_GMAIL_CLIENT_SECRET</key><string>/Users/YOUR_USER/client_secret.json</string> <key>BRIEFED_GMAIL_TOKEN_FILE</key><string>/Users/YOUR_USER/.openclaw/workspace/briefed-gmail-token.json</string> </dict> <key>RunAtLoad</key><true/> <key>KeepAlive</key><true/> <key>WorkingDirectory</key><string>/Users/YOUR_USER/.openclaw/workspace/briefed</string> <key>StandardOutPath</key><string>/tmp/briefed.log</string> <key>StandardErrorPath</key><string>/tmp/briefed.log</string> </dict></plist> ``` ```bash launchctl load ~/Library/LaunchAgents/ai.openclaw.briefed.plist ``` ```text Cron schedule: `0 7 * * *` (7am daily), model: `anthropic/claude-haiku-4-5`, delivery: `announce`. ``` ### Technical Analysis The documented setup establishes two cross-session execution mechanisms: 1. A macOS LaunchAgent starts the Node.js reader when the user logs in. 2. `KeepAlive` causes launchd to restart the process after it terminates. 3. A daily OpenClaw cron agent invokes the Gmail retrieval and body-fetching scripts. 4. The persistent service configuration receives paths associated with Gmail OAuth credentials. The behavior is disclosed and supports automatic digest delivery, rather than being hidden. However, persistence is not required for the core functionality because the reader and ...[truncated 1322 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Make manual execution the default installation mode. - Do not create or load a LaunchAgent without separate, explicit user consent. - Avoid `KeepAlive`; if automatic startup is needed, use a narrowly scoped scheduled invocation. - Provide complete removal instructions: ```bash launchctl unload ~/Library/LaunchAgents/ai.openclaw.briefed.plist rm ~/Library/LaunchAgents/ai.openclaw.briefed.plist ``` - Provide corresponding instructions for listing, disabling, and deleting the OpenClaw cron task. - Do not place credential values in service files. Pass only a protected token path when strictly necessary. - Ensure the referenced workspace and scripts are owned by the user and are not writable by other accounts. - Clearly state the execution frequency, files accessed, network services contacted, and credential scope before enabling persistence. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
assets/reader/server.js:16
Finding
Unauthenticated Reader API May Expose Private Email Content and Writable State<![CDATA[ ## Vulnerability Details **File Location**: `assets/reader/server.js:16-17`, `assets/reader/server.js:21-152`, `assets/reader/server.js:202-206` **Vulnerability Type**: Missing authentication and unrestricted network binding **Risk Level**: High ### Vulnerable Code ```javascript app.use(express.json()); app.use(express.static(path.join(__dirname, 'public'))); ``` ```javascript // GET /api/today — return today's stories app.get('/api/today', (req, res) => { try { if (!fs.existsSync(STORIES_FILE)) { return res.status(404).json({ error: 'No stories file found. Run the newsletter digest first.' }); } const data = JSON.parse(fs.readFileSync(STORIES_FILE, 'utf8')); const light = { ...data, stories: data.stories.map(s => ({ ...s, hasBody: !!(s.body), body: undefined })) }; res.json(light); } catch (err) { console.error('Error reading stories:', err); res.status(500).json({ error: 'Failed to read stories file.' }); } }); ``` ```javascript // GET /api/story/:id — fetch single story body on demand app.get('/api/story/:id', (req, res) => { try { if (!fs.existsSync(STORIES_FILE)) { return res.status(404).json({ error: 'No stories file.' }); } const data = JSON.parse(fs.readFileSync(STORIES_FILE, 'utf8')); const story = data.stories.find(s => s.id === req.params.id); if (!story) return res.status(404).json({ error: 'Story not found.' }); res.json({ id: story.id, body: story.body || '' }); } catch (err) { res.status(500).json({ error: 'Failed to read story.' }); } }); ``` ```javascript // GET /api/notes — return all saved notes app.get('/api/notes', (req, res) => { try { const notes = readNotes(); res.json(notes); } catch (err) { res.status(500).json({ error: 'Failed to read notes.' }); } }); ``` ```javascript // Start server app.listen(PORT, () => { console.log(`📰 Newsletter Reader running at http://localhost:${P ...[truncated 2272 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Explicitly bind the local-only reader to loopback: ```javascript app.listen(PORT, '127.0.0.1', () => { console.log(`Newsletter Reader running at http://127.0.0.1:${PORT}`); }); ``` - If remote access is supported, require authenticated sessions for every API route. - Add authorization checks before returning email bodies or notes. - Add CSRF protection to all state-changing endpoints. - Validate `Origin` and `Host` headers against an explicit allowlist. - Set a strict request-body limit: ```javascript app.use(express.json({ limit: '32kb' })); ``` - Add security headers, including an appropriate Content Security Policy, using a maintained middleware such as Helmet. - Add rate limiting and request logging suitable for sensitive endpoints. - Warn users not to expose or forward port 3001 unless authentication and TLS are enabled. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
assets/reader/public/app.js:493
Finding
Untrusted Email HTML Loads Sender-Controlled Tracking Resources<![CDATA[ ## Vulnerability Details **File Location**: `scripts/fetch-bodies.py:64-70`, `assets/reader/scripts/fetch-bodies.py:64-70`, `assets/reader/public/app.js:493-522` **Vulnerability Type**: Inadequate HTML sanitization and remote resource loading **Risk Level**: Medium ### Vulnerable Code The same incomplete sanitizer exists in both copies of `fetch-bodies.py`: ```python def clean_html(html): html = re.sub(r'<script[^>]*>.*?</script>', '', html, flags=re.DOTALL | re.IGNORECASE) html = re.sub(r'<img[^>]*(width=["\']?1["\']?|height=["\']?1["\']?)[^>]*/?>', '', html, flags=re.IGNORECASE) html = re.sub(r'(width\s*=\s*["\']?)(\d{4,})', lambda m: m.group(1) + '100%', html) return html ``` The resulting untrusted HTML is inserted into an iframe document: ```javascript function setModalBody(html) { if (!modalFrame) return; const wrapped = [ '<html><head>', '<meta name="viewport" content="width=device-width,initial-scale=1">', '<style>', '* { box-sizing: border-box; }', 'body { margin: 0 !important; padding: 16px 18px !important; background: #fff; }', 'img { max-width: 100% !important; height: auto !important; }', 'table { max-width: 100% !important; }', '</style>', '</head><body>', html, '</body></html>', ].join(''); modalFrame.onload = function() { try { const doc = modalFrame.contentDocument; if (!doc) return; doc.querySelectorAll('a[href]').forEach(function(a) { a.target = '_blank'; a.rel = 'noopener noreferrer'; }); } catch(e) {} }; modalFrame.srcdoc = wrapped; } ``` The iframe is created with this sandbox: ```html <iframe class="modal-frame" sandbox="allow-same-origin allow-popups allow-popups-to-escape-sandbox" title="Email content"> </iframe> ``` ### Technical Analysis The regular-expression sanitizer removes conventional `<script>` blocks and only some tracking pixels with explicit one-pixel `width` or `height` attribut ...[truncated 1977 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Replace regular-expression filtering with a maintained, allowlist-based HTML sanitizer. - Remove all remote resource-loading attributes and CSS URLs by default. - Permit only safe presentation elements and attributes. - Rewrite or remove `src`, `srcset`, `poster`, `background`, external `<link>` elements, and CSS `url(...)` values. - Add a restrictive Content Security Policy inside the iframe document, for example: ```html <meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src data:; style-src 'unsafe-inline'; font-src data:"> ``` - Provide an explicit “Load remote images” action if users need original newsletter images. - If remote images are supported, retrieve them through a privacy-preserving proxy that removes cookies, authentication headers, referrers, and recipient-specific query parameters. - Apply the same corrected sanitizer to both copies of `fetch-bodies.py` to prevent deployment-dependent behavior. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/pre-fetch.py:83
Finding
Reusable Gmail OAuth Token Is Written Without Explicit Restrictive Permissions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/pre-fetch.py:83-85`, `scripts/fetch-bodies.py:39-41`, `assets/reader/scripts/pre-fetch.py:83-85`, `assets/reader/scripts/fetch-bodies.py:39-41` **Vulnerability Type**: Insecure local credential storage **Risk Level**: Medium ### Vulnerable Code ```python os.makedirs(os.path.dirname(TOKEN_FILE), exist_ok=True) with open(TOKEN_FILE, 'w') as token: token.write(creds.to_json()) ``` ### Technical Analysis The scripts persist a reusable Gmail OAuth credential to disk using the process's current umask. They do not explicitly create the containing directory with owner-only permissions, create the token as mode `0600`, verify file ownership, or reject symbolic links. The token is generated with the `gmail.readonly` scope. Although this scope cannot modify or delete messages, a refresh token can provide ongoing read access to the user's Gmail account until revoked. The default location under the user's home directory may inherit safe permissions on many systems, but that is an environmental assumption rather than an enforced security control. The configurable `BRIEFED_GMAIL_TOKEN_FILE` path increases the likelihood of writing to an unsafe directory or pre-existing path. ### Attack Path 1. A local attacker identifies a shared or permissively accessible token directory, or prepares a symbolic link at a custom token path. 2. The user runs the OAuth flow. 3. The script opens the existing path with write mode and follows the symbolic link if present. 4. The serialized OAuth credential is written with permissions determined by the process umask. 5. The local attacker reads the file or obtains the credential from the redirected location. 6. The attacker uses the refresh token against Google's OAuth service and reads Gmail through the authorized scope. ### Impact Assessment Successful exploitation can provide persistent read access to Gmail data available under the granted scope. This includes messages ...[truncated 338 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Create the credential directory with mode `0700`. - Create the token file atomically with owner-only mode `0600`. - Reject symbolic links and verify that an existing token is a regular file owned by the current user. - Apply restrictive permissions after every update. - Use an atomic temporary-file-and-rename pattern in the same protected directory. - Prefer the operating system's credential store or keychain where available. Example hardening pattern: ```python token_dir = os.path.dirname(TOKEN_FILE) os.makedirs(token_dir, mode=0o700, exist_ok=True) os.chmod(token_dir, 0o700) flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC if hasattr(os, "O_NOFOLLOW"): flags |= os.O_NOFOLLOW fd = os.open(TOKEN_FILE, flags, 0o600) try: with os.fdopen(fd, "w") as token: token.write(creds.to_json()) finally: os.chmod(TOKEN_FILE, 0o600) ``` Additional ownership and regular-file checks should be performed before accepting an existing credential file. ]]>

T08 · Insecure Dependencies

Note
Location
scripts/requirements.txt:1
Finding
Python Security-Sensitive Dependencies Are Not Reproducibly Pinned<![CDATA[ ## Vulnerability Details **File Location**: `scripts/requirements.txt:1-3`, `assets/reader/scripts/requirements.txt:1-3`, `SKILL.md:65-69` **Vulnerability Type**: Unbounded dependency resolution for OAuth and Gmail libraries **Risk Level**: Low ### Vulnerable Code ```text google-api-python-client>=2.160.0 google-auth>=2.38.0 google-auth-oauthlib>=1.2.1 ``` The documented installation command is: ```bash python3 -m pip install -r scripts/requirements.txt ``` ### Technical Analysis The requirements specify only minimum versions. Every installation can therefore resolve to different future direct and transitive dependency versions. The package names and expected package source are not suspicious, and no malicious package was identified during the static review. The risk is a lack of reproducibility: an incompatible, compromised, or unexpectedly changed future release could be installed without a corresponding Skill review. These libraries process OAuth client secrets, refresh tokens, and Gmail content, making dependency integrity important even though the current package names are legitimate. ### Attack Path 1. The user runs the documented pip installation command. 2. pip resolves the newest versions satisfying the lower bounds. 3. A future compromised or unsafe direct or transitive release is selected. 4. The package executes during installation or is imported by the Gmail scripts. 5. The affected dependency runs with the user's privileges and may access OAuth credentials or fetched email data. This is a supply-chain exposure rather than evidence that the currently named packages are malicious. ### Impact Assessment A compromised dependency could execute arbitrary code with the privileges of the user running pip or the digest scripts. It could potentially access the Gmail OAuth token, OAuth client file, newsletter data, and other files accessible to that user. The practical likelihood is lower than the application-level findings because e ...[truncated 96 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Pin reviewed direct and transitive dependencies to exact versions. - Generate a lock file containing cryptographic hashes. - Install with hash verification, for example: ```bash python3 -m pip install --require-hashes -r requirements.lock ``` - Install dependencies in an isolated virtual environment rather than the user's global Python environment. - Use an automated dependency-review process before updating the lock file. - Keep the duplicate requirements files synchronized or remove duplication. - For the Node.js application, prefer `npm ci` with the committed lockfile rather than `npm install` so deployment uses the reviewed dependency graph. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Memory PoisoningPersistent Context Injection, Context Window Stuffing, Memory Manipulation
  • Rogue AgentSelf-Modification, Session Persistence
Findings (40)

Known Vulnerable Dependency: path-to-regexp==0.1.12 — 1 advisory(ies): CVE-2024-45296 (path-to-regexp vulnerable to Regular Expression Denial of Service via multiple r)

High
Category
Supply Chain
Confidence
96% confidence
Finding
path-to-regexp 0.1.12 is a real high-risk dependency issue because Express 4 uses it for route matching, and the cited ReDoS means crafted paths can force excessive backtracking and block the Node.js event loop. Since this skill serves a web reader app, an attacker able to send HTTP requests to the local server could cause the app to hang or become unresponsive.

Memory Manipulation

High
Category
Memory Poisoning
Content
// Toggle vote off if same, otherwise switch
  if (prevVote === action) {
    delete state.votes[storyId];
  } else {
    state.votes[storyId] = action;
  }
Confidence
80% confidence
Finding
Skill manipulates agent memory, state, or stored context. Memory corruption can alter personality, override safety rules, or cause unpredictable behavior.

Memory Manipulation

High
Category
Memory Poisoning
Content
// Toggle vote off if same, otherwise switch
  if (prevVote === action) {
    delete state.votes[storyId];
  } else {
    state.votes[storyId] = action;
  }
Confidence
80% confidence
Finding
Skill manipulates agent memory, state, or stored context. Memory corruption can alter personality, override safety rules, or cause unpredictable behavior.

Credential Access

High
Category
Privilege Escalation
Content
STORIES_FILE = os.path.join(os.path.dirname(__file__), '..', '..', 'newsletter-today.json')
TOKEN_FILE = os.environ.get('BRIEFED_GMAIL_TOKEN_FILE', os.path.expanduser('~/.openclaw/workspace/briefed-gmail-token.json'))
CLIENT_SECRET_FILE = os.environ.get('BRIEFED_GMAIL_CLIENT_SECRET', os.path.expanduser('~/client_secret.json'))
SCOPES = ['https://www.googleapis.com/auth/gmail.readonly']
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
STORIES_FILE = os.path.join(os.path.dirname(__file__), '..', '..', 'newsletter-today.json')
TOKEN_FILE = os.environ.get('BRIEFED_GMAIL_TOKEN_FILE', os.path.expanduser('~/.openclaw/workspace/briefed-gmail-token.json'))
CLIENT_SECRET_FILE = os.environ.get('BRIEFED_GMAIL_CLIENT_SECRET', os.path.expanduser('~/client_secret.json'))
SCOPES = ['https://www.googleapis.com/auth/gmail.readonly']
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
STORIES_FILE = os.path.join(os.path.dirname(__file__), '..', '..', 'newsletter-today.json')
TOKEN_FILE = os.environ.get('BRIEFED_GMAIL_TOKEN_FILE', os.path.expanduser('~/.openclaw/workspace/briefed-gmail-token.json'))
CLIENT_SECRET_FILE = os.environ.get('BRIEFED_GMAIL_CLIENT_SECRET', os.path.expanduser('~/client_secret.json'))
SCOPES = ['https://www.googleapis.com/auth/gmail.readonly']
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
STORIES_FILE = os.path.join(os.path.dirname(__file__), '..', '..', 'newsletter-today.json')
TOKEN_FILE = os.environ.get('BRIEFED_GMAIL_TOKEN_FILE', os.path.expanduser('~/.openclaw/workspace/briefed-gmail-token.json'))
CLIENT_SECRET_FILE = os.environ.get('BRIEFED_GMAIL_CLIENT_SECRET', os.path.expanduser('~/client_secret.json'))
SCOPES = ['https://www.googleapis.com/auth/gmail.readonly']
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
STORIES_FILE = os.path.join(os.path.dirname(__file__), '..', '..', 'newsletter-today.json')
TOKEN_FILE = os.environ.get('BRIEFED_GMAIL_TOKEN_FILE', os.path.expanduser('~/.openclaw/workspace/briefed-gmail-token.json'))
CLIENT_SECRET_FILE = os.environ.get('BRIEFED_GMAIL_CLIENT_SECRET', os.path.expanduser('~/client_secret.json'))
SCOPES = ['https://www.googleapis.com/auth/gmail.readonly']
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
STORIES_FILE = os.path.join(os.path.dirname(__file__), '..', '..', 'newsletter-today.json')
TOKEN_FILE = os.environ.get('BRIEFED_GMAIL_TOKEN_FILE', os.path.expanduser('~/.openclaw/workspace/briefed-gmail-token.json'))
CLIENT_SECRET_FILE = os.environ.get('BRIEFED_GMAIL_CLIENT_SECRET', os.path.expanduser('~/client_secret.json'))
SCOPES = ['https://www.googleapis.com/auth/gmail.readonly']
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
STORIES_FILE = os.path.join(os.path.dirname(__file__), '..', '..', 'newsletter-today.json')
TOKEN_FILE = os.environ.get('BRIEFED_GMAIL_TOKEN_FILE', os.path.expanduser('~/.openclaw/workspace/briefed-gmail-token.json'))
CLIENT_SECRET_FILE = os.environ.get('BRIEFED_GMAIL_CLIENT_SECRET', os.path.expanduser('~/client_secret.json'))
SCOPES = ['https://www.googleapis.com/auth/gmail.readonly']
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
STORIES_FILE = os.path.join(os.path.dirname(__file__), '..', '..', 'newsletter-today.json')
TOKEN_FILE = os.environ.get('BRIEFED_GMAIL_TOKEN_FILE', os.path.expanduser('~/.openclaw/workspace/briefed-gmail-token.json'))
CLIENT_SECRET_FILE = os.environ.get('BRIEFED_GMAIL_CLIENT_SECRET', os.path.expanduser('~/client_secret.json'))
SCOPES = ['https://www.googleapis.com/auth/gmail.readonly']
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The README describes ingesting Gmail newsletter content, processing it with an external model, and exposing a local web app, but it does not clearly warn users about privacy, data-sharing, or local exposure implications. This can lead users to grant mailbox access or run the service without understanding that email contents may be transmitted to third-party AI services and made available through a local server.

Lp3

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The skill declares access to environment variables, file writes, and networked services, but does not explicitly scope or constrain those capabilities via a permissions or allowed-tools declaration. That creates an ambient-authority situation where an agent executing the skill may have broader access than the skill description suggests, increasing the chance of overreach or abuse if downstream instructions or code are modified.

Session Persistence

Medium
Category
Rogue Agent
Content
- Gmail access is **read-only** (`gmail.readonly`).
- OAuth token is stored locally at `~/.openclaw/workspace/briefed-gmail-token.json` (or `BRIEFED_GMAIL_TOKEN_FILE`).
- The workflow should only read/write the following workspace files:
  - `newsletter-inbox.json`
  - `newsletter-today.json`
  - `newsletter-interests.json`
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Session Persistence

Medium
Category
Rogue Agent
Content
# Quick test
node ~/.openclaw/workspace/briefed/server.js

# Persistent — create ~/Library/LaunchAgents/ai.openclaw.briefed.plist
```

LaunchAgent plist template:
Confidence
75% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Session Persistence

Medium
Category
Rogue Agent
Content
# Quick test
node ~/.openclaw/workspace/briefed/server.js

# Persistent — create ~/Library/LaunchAgents/ai.openclaw.briefed.plist
```

LaunchAgent plist template:
Confidence
75% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Session Persistence

Medium
Category
Rogue Agent
Content
# Quick test
node ~/.openclaw/workspace/briefed/server.js

# Persistent — create ~/Library/LaunchAgents/ai.openclaw.briefed.plist
```

LaunchAgent plist template:
Confidence
75% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Session Persistence

Medium
Category
Rogue Agent
Content
# Quick test
node ~/.openclaw/workspace/briefed/server.js

# Persistent — create ~/Library/LaunchAgents/ai.openclaw.briefed.plist
```

LaunchAgent plist template:
Confidence
75% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Session Persistence

Medium
Category
Rogue Agent
Content
# Quick test
node ~/.openclaw/workspace/briefed/server.js

# Persistent — create ~/Library/LaunchAgents/ai.openclaw.briefed.plist
```

LaunchAgent plist template:
Confidence
75% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Session Persistence

Medium
Category
Rogue Agent
Content
# Quick test
node ~/.openclaw/workspace/briefed/server.js

# Persistent — create ~/Library/LaunchAgents/ai.openclaw.briefed.plist
```

LaunchAgent plist template:
Confidence
75% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Session Persistence

Medium
Category
Rogue Agent
Content
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
  <key>Label</key><string>ai.openclaw.briefed</string>
  <key>ProgramArguments</key><array>
Confidence
75% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Session Persistence

Medium
Category
Rogue Agent
Content
```

```bash
launchctl load ~/Library/LaunchAgents/ai.openclaw.briefed.plist
```

### 7. Create the daily cron job
Confidence
75% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
Opening an email modal automatically sends a tracking signal (`vote: 'open'`) to the server without explicit notice or consent. In a newsletter intelligence tool that processes personal inbox content and interest patterns, this creates undisclosed behavioral telemetry that can reveal reading habits and inferred preferences.

Missing User Warnings

Medium
Confidence
84% confidence
Finding
The script pulls Gmail metadata and snippets, then persists them to newsletter-inbox.json on disk without any visible consent prompt, retention control, or file-permission hardening. Because email snippets and subjects frequently contain sensitive personal or commercial information, local plaintext persistence can expose mailbox data to other local users, backup systems, or later components that were not intended to handle raw inbox content.

Tainted flow: 'TOKEN_FILE' from os.environ.get (line 17, credential/environment) → open (file write)

Medium
Category
Data Flow
Content
creds = flow.run_local_server(port=0, open_browser=True)

        os.makedirs(os.path.dirname(TOKEN_FILE), exist_ok=True)
        with open(TOKEN_FILE, 'w') as token:
            token.write(creds.to_json())

    return build('gmail', 'v1', credentials=creds, cache_discovery=False)
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Static analysis

No suspicious patterns detected.