Skip to content

fix(security): resolve Aikido findings — command injection, path traversal, prototype pollution#77

Open
Nivesh353 wants to merge 3 commits into
open-gitagent:mainfrom
Nivesh353:fix/aikido-security-findings
Open

fix(security): resolve Aikido findings — command injection, path traversal, prototype pollution#77
Nivesh353 wants to merge 3 commits into
open-gitagent:mainfrom
Nivesh353:fix/aikido-security-findings

Conversation

@Nivesh353

Copy link
Copy Markdown
Contributor

Summary

Addresses the security findings from the Aikido scan by hardening how
untrusted input (tool args, plugin manifests, config values, branch/skill
names) is handled across the codebase. No user-facing feature changes.

What changed

Command injection (CWE-78)
Replaced shell-interpolated execSync("git …") with
execFileSync("git", [args]) so names/paths can't break out into shell
commands.

  • session.ts — all git ops (clone/fetch/checkout/commit/push)
  • loader.ts — git clone for extends/dependencies
  • tools/capture-photo.ts, tools/memory.ts, tools/skill-learner.ts — git add/commit

Path traversal (CWE-22)
Confine file access to the intended base directory.

  • knowledge.ts — skip entries whose path escapes the knowledge dir
  • tool-loader.ts — reject tool scripts outside tools/; add runtime
    allowlist (sh/bash/node/python3/python)
  • plugins.tsresolveWithinDir() guards plugin entry/prompt paths
  • tools/memory.ts — memory-layer path must stay within the agent dir
  • chat-history.tssanitizeBranch() now strips \ and .. (not just
    /); exported and reused by context.ts

Input validation
Require safe kebab-case/identifier names before they're used to build
filesystem paths: schedules (id), workflows (name), plugin-cli +
plugins (plugin name), skill-learner (skill_name), task-tracker
(skill_used), config (env), loader (dep name).

Prototype pollution (CWE-1321)
deepMerge() ignores __proto__ / constructor / prototype keys in
config.ts and loader.ts.

Dependencies / CI hardening

  • package.jsonoverrides for basic-ftp and uuid (patch vulnerable
    transitive deps)
  • publish.ymlpersist-credentials: false on checkout; pin Node to 22.14.0

Testing

  • tsc compiles clean
  • Existing unit tests pass (27/27)
  • Security guards verified against the real shipped tool code (path
    traversal, kebab-case validation, branch sanitizing) — all blocking/
    allowing as expected
  • Confirmed the sanitizeBranch change does not affect the voice web
    UI (branch listing reads real git branches; filenames are unchanged for
    all valid branch names)

- Command injection (CWE-78): swap shell-based execSync("git …") for
  execFileSync("git", [args]) in session, loader, and the capture-photo,
  memory, and skill-learner tools.
- Path traversal (CWE-22): confine paths to their base dir in knowledge,
  tool-loader (+ runtime allowlist), plugins (entry/prompt), and the
  memory tool; sanitizeBranch now strips "\" and ".." too.
- Input validation: enforce kebab-case/safe names before building paths
  in schedules, workflows, plugin-cli, plugins, skill-learner,
  task-tracker, config, and loader.
- Prototype pollution (CWE-1321): deepMerge ignores __proto__/constructor/
  prototype in config and loader.
- Deps/CI: add basic-ftp and uuid overrides; set persist-credentials:false
  and pin Node 22.14.0 in publish.yml.

@shreyas-lyzr shreyas-lyzr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solid security hardening overall — the execFileSync migrations, deepMerge prototype-pollution guards, and path-traversal checks with resolve/relative are correct and well-applied. A few issues worth addressing before merge.

Scope creep in package.json

The PR description says no user-facing feature changes, but package.json adds @open-gitagent/voice as a hard production dependency (not in the base branch) and pulls in a large transitive graph (baileys, whatsapp-rust-bridge, pino, sharp, libsignal/GPL-3.0, etc.). That is unrelated to the Aikido findings and belongs in a separate PR. If this is an accidental rebase artifact it should be reverted. If intentional, it needs its own review including license due diligence on libsignal (GPL-3.0 in a production dependency is a concern for a library codebase).

knowledge.ts: path traversal guard only covers always_load entries

The traversal check in the diff protects preloaded entries, but the available entries array (the else branch, line 56 in the file) has no guard. If the read tool later joins knowledgeDir + entry.path without its own validation, a malicious index.yaml could still escape the knowledge directory for on-demand reads. Apply the same resolve/relative check before pushing to available, or confirm that on-demand reads are independently guarded elsewhere.

sanitizeBranch: non-iterative replacement leaves trailing dot

The .replace(/../g, '') is a single pass, so the string '...' (three dots) becomes '.' — the trailing dot survives. The result does not re-introduce a traversal segment, so this is not directly exploitable as written. However an allowlist replace such as branch.replace(/[^a-zA-Z0-9_-]/g, '__') is simpler to audit, or the final historyPath join result could be validated with resolve/relative like the other guards in this PR.

Inconsistent name validation between install and load

installPlugin sanitizes the derived name with /[^a-zA-Z0-9_-]/g (allows uppercase and underscores), while discoverAndLoadPlugins validates with /^[a-zA-Z0-9_-]+$/ (also allows uppercase+underscore), and manifest validation uses KEBAB_RE (/^[a-z0-9]+(-[a-z0-9]+)*$/). A plugin whose repo name contains uppercase letters installs successfully under a name that then fails KEBAB_RE at load time. Normalize to lowercase kebab at install time and enforce a single regex everywhere.

Comment thread src/chat-history.ts Outdated
Comment thread package.json Outdated
Comment thread src/plugins.ts Outdated

@shreyas-lyzr shreyas-lyzr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All four issues from the previous review are addressed:

  1. The @open-gitagent/voice dependency (and its GPL-3.0 transitive chain) is absent from this revision — clean.

  2. knowledge.ts path traversal guard now covers the full entry loop before the always_load branch, so on-demand (available) entries are subject to the same resolve/relative check. The original blocking concern is resolved.

  3. sanitizeBranch is now an allowlist (/[^a-zA-Z0-9_-]/g → ''), which is trivially auditable and closes the '...' → '.' edge case noted earlier.

  4. installPlugin normalizes derived names to lowercase kebab-case before writing to disk, so a plugin that installs successfully will always pass KEBAB_RE at load time. The install/load mismatch is gone.

Additional hardening in this revision — execFileSync across all git ops, deepMerge prototype-pollution guards in both config.ts and loader.ts, runtime allowlist in tool-loader.ts, resolveWithinDir() for plugin entry/prompt, input validation on schedule/workflow/skill/dep names — all look correct. The allowlist approach is consistently applied and the guards are placed at the right boundary (before any path join is used for I/O).

No new issues found. Good to merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants