Back to skill

Security audit

Clawfeed

Security checks for vulnerabilities and agentic risk

Overview

ClawFeed appears to be a real news digest app, but it needs Review because some server and test behavior can affect more than a user would reasonably expect.

Review before installing or deploying. Run it only in a controlled local or protected environment, set strong API/session/OAuth secrets, do not expose it publicly without fixing the source-resolver SSRF controls, and do not run npm test unless AI_DIGEST_API and AI_DIGEST_FEED point to an isolated local test server. Use HTTPS-only feedback webhooks and avoid putting API keys in URLs.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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
Findings (6)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
src/server.mjs:131
Finding
Source resolution permits SSRF through incomplete IP validation and DNS rebinding<![CDATA[ ## Vulnerability Details **File Location**: `src/server.mjs:131-229`, reachable through `src/server.mjs:612-625` **Vulnerability Type**: Server-Side Request Forgery **Risk Level**: High ### Vulnerable Code ```js function isPrivateOrSpecialIp(ip) { if (!ip) return true; if (ip.includes(':')) { const n = ip.toLowerCase(); return n === '::1' || n.startsWith('fc') || n.startsWith('fd') || n.startsWith('fe80:') || n.startsWith('::ffff:127.'); } const p = ip.split('.').map(Number); if (p.length !== 4 || p.some((x) => Number.isNaN(x) || x < 0 || x > 255)) return true; const [a, b] = p; return ( a === 0 || a === 10 || a === 127 || (a === 169 && b === 254) || (a === 172 && b >= 16 && b <= 31) || (a === 192 && b === 168) || a >= 224 ); } async function assertSafeFetchUrl(rawUrl) { const u = new URL(rawUrl); if (!['http:', 'https:'].includes(u.protocol)) throw new Error('invalid url scheme'); const host = u.hostname; if (host === 'localhost' || host.endsWith('.localhost')) throw new Error('blocked host'); if (isIP(host) && isPrivateOrSpecialIp(host)) throw new Error('blocked host'); const resolved = await lookup(host, { all: true }); if (!resolved.length || resolved.some((r) => isPrivateOrSpecialIp(r.address))) { throw new Error('blocked host'); } } async function httpFetch(url, timeout = 5000, redirectsLeft = 3) { await assertSafeFetchUrl(url); return new Promise((resolve, reject) => { const mod = url.startsWith('https') ? https : http; const r = mod.get(url, { headers: { 'User-Agent': 'AI-Digest/1.0', 'Accept': 'text/html,application/xhtml+xml,application/xml,application/json,*/*' } }, async (resp) => { try { if (resp.statusCode >= 300 && resp.statusCode < 400 && resp.headers.location) { clearTimeout(timer); if (redirectsLeft <= 0) return reject(new Error('too many redirects')); const nextUrl = ...[truncated 3562 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse addresses with a well-tested IP-range library supporting IPv4, IPv6, and IPv4-mapped IPv6 normalization. 2. Reject all loopback, private, link-local, multicast, unspecified, reserved, documentation, carrier-grade NAT, and non-global addresses. 3. Normalize IPv4-mapped IPv6 addresses and apply the complete IPv4 policy to the embedded address. 4. Resolve the hostname once, validate every returned address, and connect directly to a validated address. 5. Preserve the original hostname only for the HTTP `Host` header and TLS SNI validation. 6. Revalidate every redirect target and enforce a small redirect limit. 7. Consider an allowlist of approved feed domains or route source retrieval through a restricted egress proxy. 8. Add tests covering mapped IPv6, mixed public/private DNS answers, DNS rebinding, alternate numeric address forms, and cloud metadata addresses. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
test/e2e.sh:10
Finding
E2E tests perform destructive authenticated operations against a remote service by default<![CDATA[ ## Vulnerability Details **File Location**: `test/e2e.sh:10-11`, with remote mutations throughout `test/e2e.sh:97-443` **Vulnerability Type**: Unsafe production-default test configuration **Risk Level**: High ### Vulnerable Code ```bash API="${AI_DIGEST_API:-https://digest.kevinhe.io/api}" FEED="${AI_DIGEST_FEED:-https://digest.kevinhe.io/feed}" SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" AI_DIGEST_DB="${AI_DIGEST_DB:-$SCRIPT_DIR/../data/digest.db}" ALICE="Cookie: session=test-sess-alice" BOB="Cookie: session=test-sess-bob" CAROL="Cookie: session=test-sess-carol" DAVE="Cookie: session=test-sess-dave" ``` Representative destructive operations include: ```bash A_S1=$(curl -s -X POST "$API/sources" -H "$ALICE" \ -H "Content-Type: application/json" \ -d '{"name":"Alice Public RSS","type":"rss","config":"{\"url\":\"https://alice.test/rss\"}","isPublic":true}' | jq_val "d['id']") A_S2=$(curl -s -X POST "$API/sources" -H "$ALICE" \ -H "Content-Type: application/json" \ -d '{"name":"Alice Public HN","type":"hackernews","config":"{\"section\":\"front\"}","isPublic":true}' | jq_val "d['id']") A_S3=$(curl -s -X POST "$API/sources" -H "$ALICE" \ -H "Content-Type: application/json" \ -d '{"name":"Alice Private Reddit","type":"reddit","config":"{\"subreddit\":\"test\"}","isPublic":false}' | jq_val "d['id']") r=$(curl -s -X DELETE "$API/sources/$A_S3" -H "$ALICE") ``` Later tests continue to mutate and delete server data: ```bash curl -s -X DELETE "$API/sources/$A_S2" -H "$ALICE" > /dev/null SD_SRC=$(curl -s -X POST "$API/sources" -H "$ALICE" \ -H "Content-Type: application/json" \ -d '{"name":"SoftDel Test","type":"rss","config":"{\"url\":\"https://softdel.test/rss\"}","isPublic":true}' | jq_val "d['id']") curl -s -X DELETE "$API/sources/$SD_SRC" -H "$ALICE" > /dev/null ``` The package entry point invokes this script directly: ```json { "scripts": { "test": "bash test/e2e.sh" } } ``` ### Technical Analysis Running `npm ...[truncated 1655 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Change defaults to loopback addresses such as `http://127.0.0.1:8767/api`. 2. Refuse to run destructive tests unless the target hostname is loopback or an explicit acknowledgment such as `ALLOW_REMOTE_DESTRUCTIVE_TESTS=yes` is supplied. 3. Remove fixed reusable session identifiers. 4. Create random, short-lived test users and sessions in an isolated temporary database. 5. Start an isolated application instance as part of the test lifecycle. 6. Add a shell `trap` that removes all created records and terminates the isolated server on success, failure, or interruption. 7. Display the target URL and require confirmation for any non-loopback interactive run. 8. Separate read-only remote smoke tests from destructive local E2E tests. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
src/server.mjs:33
Finding
OAuth state protection can fall back to a public constant and is not bound to the initiating browser<![CDATA[ ## Vulnerability Details **File Location**: `src/server.mjs:33`, `src/server.mjs:111-126`, and `src/server.mjs:432-466` **Vulnerability Type**: OAuth login CSRF and weak state integrity **Risk Level**: Medium ### Vulnerable Code ```js const OAUTH_STATE_SECRET = env.OAUTH_STATE_SECRET || process.env.OAUTH_STATE_SECRET || SESSION_SECRET || API_KEY || 'dev-state-secret'; ``` ```js function signOAuthState(payload) { const body = Buffer.from(JSON.stringify(payload)).toString('base64url'); const sig = createHmac('sha256', OAUTH_STATE_SECRET) .update(body) .digest('base64url'); return `${body}.${sig}`; } function verifyOAuthState(state) { if (!state || !state.includes('.')) return null; const [body, sig] = state.split('.', 2); const expected = createHmac('sha256', OAUTH_STATE_SECRET) .update(body) .digest('base64url'); const a = Buffer.from(sig); const b = Buffer.from(expected); if (a.length !== b.length || !timingSafeEqual(a, b)) return null; try { return JSON.parse(Buffer.from(body, 'base64url').toString()); } catch { return null; } } ``` ```js const nonce = randomBytes(16).toString('hex'); const state = signOAuthState({ origin, redirectUri, nonce, ts: Date.now() }); ``` ```js const st = verifyOAuthState(stateRaw); if (!st) return json(res, { error: 'invalid oauth state' }, 400); if (Date.now() - (st.ts || 0) > 10 * 60 * 1000) { return json(res, { error: 'expired oauth state' }, 400); } if (!isAllowedOrigin(st.origin)) { return json(res, { error: 'origin not allowed' }, 400); } origin = st.origin; redirectUri = st.redirectUri || redirectUri; ``` ### Technical Analysis If OAuth client credentials are configured while `OAUTH_STATE_SECRET`, `SESSION_SECRET`, and `API_KEY` are absent, state signatures use the known literal `dev-state-secret`. An attacker can therefore construct arbitrary state values for allowed origins. In addition, the generated nonce is only embedded inside the signed sta ...[truncated 1692 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Refuse to enable OAuth unless a dedicated, cryptographically random state secret is configured. 2. Remove the `dev-state-secret` fallback from production code. 3. Store the nonce server-side or in a short-lived, encrypted, same-site initiation cookie. 4. Bind the nonce to the initiating browser and expected redirect URI. 5. Verify and atomically consume the nonce during callback handling. 6. Reject reused, missing, mismatched, or expired state values. 7. Add tests proving that state issued to one browser cannot be completed by another. 8. Rotate any deployment that previously relied on the public fallback. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
src/server.mjs:790
Finding
Administrative API key is accepted through URL query parameters<![CDATA[ ## Vulnerability Details **File Location**: `src/server.mjs:790-816` **Vulnerability Type**: Sensitive credential exposure through URLs **Risk Level**: Medium ### Vulnerable Code ```js if (req.method === 'GET' && path === '/api/feedback/all') { const key = params.get('key') || ''; const authHeader = req.headers.authorization || ''; const bearerKey = authHeader.startsWith('Bearer ') ? authHeader.slice(7) : ''; if (!API_KEY || (key !== API_KEY && bearerKey !== API_KEY)) { return json(res, { error: 'invalid api key' }, 401); } return json(res, getAllFeedback(db)); } const feedbackReplyMatch = path.match(/^\/api\/feedback\/(\d+)\/reply$/); if (req.method === 'POST' && feedbackReplyMatch) { const key = params.get('key') || ''; const authHeader = req.headers.authorization || ''; const bearerKey = authHeader.startsWith('Bearer ') ? authHeader.slice(7) : ''; if (!API_KEY || (key !== API_KEY && bearerKey !== API_KEY)) { return json(res, { error: 'invalid api key' }, 401); } const body = await parseBody(req); if (!body.reply) return json(res, { error: 'reply required' }, 400); replyToFeedback( db, parseInt(feedbackReplyMatch[1]), body.reply, body.replied_by || 'agent' ); return json(res, { ok: true }); } const feedbackStatusMatch = path.match(/^\/api\/feedback\/(\d+)\/status$/); if (req.method === 'PATCH' && feedbackStatusMatch) { const key = params.get('key') || ''; const authHeader = req.headers.authorization || ''; const bearerKey = authHeader.startsWith('Bearer ') ? authHeader.slice(7) : ''; if (!API_KEY || (key !== API_KEY && bearerKey !== API_KEY)) { return json(res, { error: 'invalid api key' }, 401); } const body = await parseBody(req); const validStatuses = [ 'open', 'auto_draft', 'needs_human', 'replied', 'closed' ]; if (!validStatuses.includes(body.status)) { return json(res, { error: 'invalid status' }, 400); } u ...[truncated 1606 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove query-string API-key support entirely. 2. Require credentials through the `Authorization: Bearer` header. 3. Use separate, scoped credentials for feedback administration, digest creation, and configuration updates. 4. Prefer short-lived administrative tokens over one static shared key. 5. Redact authorization data in application, proxy, and observability logs. 6. Rotate the existing API key if it has ever appeared in request URLs. 7. Add rate limiting, audit logging, and administrative authorization checks to sensitive endpoints. 8. Use constant-time comparison for high-value API credentials where practical. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
src/server.mjs:755
Finding
Feedback identity and message content can be forwarded over plaintext HTTP<![CDATA[ ## Vulnerability Details **File Location**: `src/server.mjs:755-769` **Vulnerability Type**: Plaintext transmission of sensitive user information **Risk Level**: Medium ### Vulnerable Code ```js if (req.method === 'POST' && path === '/api/feedback') { const body = await parseBody(req); if (!body.message || !body.message.trim()) { return json(res, { error: 'message required' }, 400); } const id = createFeedback( db, req.user?.id || null, body.email || null, body.name || null, body.message.trim(), body.category || null ); // Lark channel notification (fire-and-forget) const LARK_WEBHOOK = env.FEEDBACK_LARK_WEBHOOK; if (LARK_WEBHOOK) { const userName = req.user?.name || body.name || 'Anonymous'; const userEmail = req.user?.email || body.email || ''; const notifBody = JSON.stringify({ msg_type: 'text', content: { text: `📨 New feedback #${id}\n` + `User: ${userName}${userEmail ? ' (' + userEmail + ')' : ''}\n` + `Message: "${body.message.trim().slice(0, 200)}"\n` + `Time: ${new Date().toISOString() .slice(0, 19) .replace('T', ' ')}` } }); try { const u = new URL(LARK_WEBHOOK); const mod = u.protocol === 'https:' ? https : http; const r = mod.request(u, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(notifBody) } }); r.on('error', () => {}); r.end(notifBody); } catch {} } return json(res, { ok: true, id }); } ``` ### Technical Analysis When `FEEDBACK_LARK_WEBHOOK` is configured, the server forwards the submitter's name, email address, and up to 200 characters of feedback content to an external endpoint. The implementation explicitly supports both HTTPS and plaintext HTTP. An HTTP webhook allows network observers, compromised routers, transparent proxies, and ...[truncated 1237 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Reject webhook URLs unless their scheme is exactly `https:`. 2. Restrict webhook destinations to an explicit host allowlist. 3. Validate DNS results for webhook destinations to prevent internal-network targeting. 4. Minimize notification content and omit email addresses unless operationally required. 5. Clearly disclose external feedback forwarding in `SKILL.md` and user-facing privacy notices. 6. Provide an operator option to disable identity forwarding while retaining generic notifications. 7. Use signed webhook requests or destination-specific authentication where supported. 8. Log delivery failures without logging sensitive payload contents or webhook secrets. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
src/server.mjs:480
Finding
Google OAuth access token is transmitted in the request URL<![CDATA[ ## Vulnerability Details **File Location**: `src/server.mjs:480` **Vulnerability Type**: Bearer token exposure through URL logging **Risk Level**: Low ### Vulnerable Code ```js const userResp = await httpsGet( `https://www.googleapis.com/oauth2/v2/userinfo?access_token=${tokens.access_token}` ); ``` ### Technical Analysis The Google access token is placed in the query string of the user-info request. TLS protects the request in transit from ordinary network interception, and the destination is Google's official HTTPS endpoint. However, URLs are more likely than authorization headers to be captured in HTTP diagnostics, proxy logs, tracing platforms, exception records, or debugging output. Bearer tokens should be placed in the `Authorization` header to reduce incidental exposure and conform to standard OAuth usage. ### Attack Path 1. A user completes Google OAuth. 2. ClawFeed receives an access token from Google's token endpoint. 3. ClawFeed constructs a user-info URL containing the complete token. 4. An HTTP client diagnostic system, proxy, tracing tool, or URL logger records the request URL. 5. A party with access to that telemetry extracts the bearer token. 6. Before expiration or revocation, the party uses the token against APIs allowed by its granted scope. ### Impact Assessment The observed OAuth scope is `openid email profile`, so the likely exposure is limited to the authenticated user's Google identity and profile information. The token does not inherently provide full Google-account access, but disclosure still permits impersonated API calls within the granted scopes until the token expires or is revoked. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions 1. Send the token in an authorization header: ```js const userResp = await httpsGet( 'https://www.googleapis.com/oauth2/v2/userinfo', { Authorization: `Bearer ${tokens.access_token}` } ); ``` 2. Update `httpsGet` to accept a controlled headers object. 3. Ensure authorization headers and token values are redacted from logs and traces. 4. Avoid including credentials in errors, URLs, metrics labels, or diagnostic output. 5. Keep OAuth scopes limited to the minimum required values. ]]>
Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (90)

Credential Access

High
Category
Privilege Escalation
Content
```bash
# 1. Copy and edit environment config
cp .env.example .env
# Edit .env with your settings

# 2. Start the API server
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Intent-Code Divergence

High
Confidence
96% confidence
Finding
The documentation explicitly claims the tool runs in read-only mode, but later describes unauthenticated write-capable endpoints such as POST /api/digests and PUT /api/config. This contradiction can mislead operators into deploying the service with weaker controls, increasing the chance of unauthorized data modification or configuration tampering.

Intent-Code Divergence

High
Confidence
97% confidence
Finding
The credential table says an API_KEY is used to protect digest creation, but the endpoint table marks the write operation as unauthenticated. This inconsistency suggests either missing access control or dangerously misleading documentation, either of which can result in exposed write functionality.

Ae1

High
Category
analysis-evasion
Content
Serve `web/index.html` via your reverse proxy or any static file server.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Tool Parameter Abuse

High
Category
Tool Misuse
Content
| 13.1 | POST /digests 无 API key | 401 |
| 13.2 | POST /sources 未登录 | 401 |
| 13.3 | POST /packs/install 未登录 | 401 |
| 13.4 | DELETE /sources 未登录 | 401 |
| 13.5 | GET /marks 未登录 | 401 |

### 14. 边界情况 (2+ cases)
Confidence
80% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Known Vulnerable Dependency: brace-expansion==1.1.12 — 4 advisory(ies): CVE-2026-13149 (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding); CVE-2026-33750 (brace-expansion: Zero-step sequence causes process hang and memory exhaustion); CVE-2026-14257 (brace-expansion: DoS via unbounded expansion length causing an out-of-memory pro) +1 more

High
Category
Supply Chain
Confidence
95% confidence
Finding
brace-expansion 1.1.12 has multiple reported denial-of-service issues involving pathological brace patterns that can trigger excessive CPU or memory consumption. Even though this instance is a transitive devDependency via minimatch/eslint tooling, it is still a genuine vulnerable package version and could be abused if attacker-controlled glob patterns reach tooling or automation.

Known Vulnerable Dependency: flatted==3.3.3 — 2 advisory(ies): CVE-2026-32141 (flatted vulnerable to unbounded recursion DoS in parse() revive phase); CVE-2026-33228 (Prototype Pollution via parse() in NodeJS flatted)

High
Category
Supply Chain
Confidence
86% confidence
Finding
flatted 3.3.3 is reported vulnerable to unbounded recursion DoS and possible prototype pollution during parse(). In this file it is only brought in through flat-cache used by eslint as a devDependency, so exploitation depends on parsing attacker-controlled serialized cache content, which reduces exposure but does not make the vulnerable version a false positive.

Known Vulnerable Dependency: js-yaml==4.1.1 — 4 advisory(ies): CVE-2026-84375 (js-yaml: maxTotalMergeKeys does not limit CPU use for empty merge sources); CVE-2026-59869 (js-yaml: YAML merge-key chains can force quadratic CPU consumption); GHSA-5p4m-2wfm-xmqj (JS-YAML: Quadratic CPU consumption in !!omap resolution (3.x and 4.x) — CVE-2026) +1 more

High
Category
Supply Chain
Confidence
93% confidence
Finding
js-yaml 4.1.1 has advisories for CPU exhaustion from crafted YAML structures such as merge-key chains and omap processing. Here it is a transitive devDependency of eslint configuration parsing, so the risk is mostly during development/CI when processing untrusted YAML or repository content, but the vulnerable version is still genuine.

Known Vulnerable Dependency: minimatch==3.1.3 — 1 advisory(ies): CVE-2026-27904 (minimatch ReDoS: nested *() extglobs generate catastrophically backtracking regu)

High
Category
Supply Chain
Confidence
94% confidence
Finding
minimatch 3.1.3 is flagged for ReDoS via crafted nested extglob patterns causing catastrophic backtracking. In this lockfile it is used transitively by eslint-related dev tooling, so exploitation is most plausible where attacker-supplied patterns are evaluated in development or CI; the package version remains vulnerable even if runtime exposure is limited.

Credential Access

High
Category
Privilege Escalation
Content
const __dirname = dirname(fileURLToPath(import.meta.url));
const ROOT = join(__dirname, '..');

// Load .env
const envPath = join(ROOT, '.env');
const env = {};
if (existsSync(envPath)) {
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
const __dirname = dirname(fileURLToPath(import.meta.url));
const ROOT = join(__dirname, '..');

// Load .env
const envPath = join(ROOT, '.env');
const env = {};
if (existsSync(envPath)) {
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
const __dirname = dirname(fileURLToPath(import.meta.url));
const ROOT = join(__dirname, '..');

// Load .env
const envPath = join(ROOT, '.env');
const env = {};
if (existsSync(envPath)) {
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
const __dirname = dirname(fileURLToPath(import.meta.url));
const ROOT = join(__dirname, '..');

// Load .env
const envPath = join(ROOT, '.env');
const env = {};
if (existsSync(envPath)) {
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
const __dirname = dirname(fileURLToPath(import.meta.url));
const ROOT = join(__dirname, '..');

// Load .env
const envPath = join(ROOT, '.env');
const env = {};
if (existsSync(envPath)) {
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
const __dirname = dirname(fileURLToPath(import.meta.url));
const ROOT = join(__dirname, '..');

// Load .env
const envPath = join(ROOT, '.env');
const env = {};
if (existsSync(envPath)) {
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
const ROOT = join(__dirname, '..');

// Load .env
const envPath = join(ROOT, '.env');
const env = {};
if (existsSync(envPath)) {
  for (const line of readFileSync(envPath, 'utf8').split('\n')) {
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
const ROOT = join(__dirname, '..');

// Load .env
const envPath = join(ROOT, '.env');
const env = {};
if (existsSync(envPath)) {
  for (const line of readFileSync(envPath, 'utf8').split('\n')) {
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
### 4. Source Ownership (3 tests)
| # | Case | Method |
|---|------|--------|
| 4.1 | Bob cannot delete Alice's source → 403 | `DELETE /sources/:id` |
| 4.2 | Alice deletes her private source | `DELETE /sources/:id` |
| 4.3 | Alice's subscription count decreases | `GET /subscriptions` |
Confidence
80% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Tool Parameter Abuse

High
Category
Tool Misuse
Content
### 4. Source Ownership (3 tests)
| # | Case | Method |
|---|------|--------|
| 4.1 | Bob cannot delete Alice's source → 403 | `DELETE /sources/:id` |
| 4.2 | Alice deletes her private source | `DELETE /sources/:id` |
| 4.3 | Alice's subscription count decreases | `GET /subscriptions` |
Confidence
80% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Tool Parameter Abuse

High
Category
Tool Misuse
Content
### 4. Source Ownership (3 tests)
| # | Case | Method |
|---|------|--------|
| 4.1 | Bob cannot delete Alice's source → 403 | `DELETE /sources/:id` |
| 4.2 | Alice deletes her private source | `DELETE /sources/:id` |
| 4.3 | Alice's subscription count decreases | `GET /subscriptions` |
Confidence
80% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Tool Parameter Abuse

High
Category
Tool Misuse
Content
### 4. Source Ownership (3 tests)
| # | Case | Method |
|---|------|--------|
| 4.1 | Bob cannot delete Alice's source → 403 | `DELETE /sources/:id` |
| 4.2 | Alice deletes her private source | `DELETE /sources/:id` |
| 4.3 | Alice's subscription count decreases | `GET /subscriptions` |
Confidence
80% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Tool Parameter Abuse

High
Category
Tool Misuse
Content
### 4. Source Ownership (3 tests)
| # | Case | Method |
|---|------|--------|
| 4.1 | Bob cannot delete Alice's source → 403 | `DELETE /sources/:id` |
| 4.2 | Alice deletes her private source | `DELETE /sources/:id` |
| 4.3 | Alice's subscription count decreases | `GET /subscriptions` |
Confidence
80% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Tool Parameter Abuse

High
Category
Tool Misuse
Content
### 4. Source Ownership (3 tests)
| # | Case | Method |
|---|------|--------|
| 4.1 | Bob cannot delete Alice's source → 403 | `DELETE /sources/:id` |
| 4.2 | Alice deletes her private source | `DELETE /sources/:id` |
| 4.3 | Alice's subscription count decreases | `GET /subscriptions` |
Confidence
80% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Tool Parameter Abuse

High
Category
Tool Misuse
Content
### 9. Subscription Management (2 tests)
| # | Case | Method |
|---|------|--------|
| 9.1 | Carol unsubscribes → count decreases | `DELETE /subscriptions/:sourceId` |
| 9.2 | Carol re-subscribes → count restores | `POST /subscriptions` |

### 10. Marks — CRUD + Isolation (7 tests)
Confidence
80% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

External Script Fetching

High
Category
Supply Chain
Content
# Visitor blocked
  check_code "Visitor cannot access marks → 401" "401" \
    "$(curl -s -o /dev/null -w '%{http_code}' "$API/marks")"

  # Alice deletes her mark
  if [ -n "$A_MARK" ] && [ "$A_MARK" != "None" ]; then
Confidence
90% confidence
Finding
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

Static analysis

Detected: suspicious.exposed_secret_literal

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
docs/STAGING.md:44

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
src/server.mjs:28