Back to skill

Security audit

session-tracker

Security checks for vulnerabilities and agentic risk

Overview

The skill is a disclosed local crash-recovery tracker that stores task progress on disk, with some operational caveats around where state is written and cleanup is run.

Before installing, set SESSION_TRACKER_DIR to a dedicated project .session directory, avoid using this skill for secret-bearing tasks, keep --fs-scan, --journal, and monitor disabled unless you need them, run cleanup --dry-run before cleanup --force, and run the bundled tests only in an isolated environment.

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
  • Output HandlingUnvalidated Output Injection, Cross-Context Output, Unbounded Output
  • Memory PoisoningPersistent Context Injection, Context Window Stuffing, Memory Manipulation
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Rogue AgentSelf-Modification, Session Persistence
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (28)

Memory Manipulation

High
Category
Memory Poisoning
Confidence
80% confidence
Finding

Skill manipulates agent memory, state, or stored context. Memory corruption can alter personality, override safety rules, or cause unpredictable behavior.

Content

Scanner excerpt · scripts/session_tracker.py (reported line 1313)May include surrounding context.

python
with open(JOURNAL, "w") as f:
            f.write(journal_backup)
        print(f"Journal PRESERVED ({len(journal_backup.strip().splitlines())} entries) — "
              f"use cleanup --purge-journal to delete history too.")
    print("All session-tracker state removed. Shared worklog/download/skills untouched.")

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
95% 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).

Content

Scanner excerpt · tests/test_session_tracker_v26.sh (reported line 35)May include surrounding context.

sh
check "task text recovered" "important task" "$OUT"

echo "=== F-01b: state.json + .bak both destroyed -> rebuild from worklog ==="
rm -f "$SESSION_TRACKER_DIR/state.json.bak"
printf '{"broken' > "$SESSION_TRACKER_DIR/state.json"
OUT=$($ST crash-detect 2>&1)
check "rebuilds from worklog" "recovered from worklog.jsonl" "$OUT"

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
95% 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).

Content

Scanner excerpt · tests/test_session_tracker_v26.sh (reported line 111)May include surrounding context.

sh
d=json.load(open('$SESSION_TRACKER_DIR/snapshot_prev.json'))
sys.exit(1 if any('/skills/' in k for k in d) else 0)"; then
  ok "snapshot contains no skills/ paths"; else bad "skills/ still enumerated into snapshot"; fi
rm -rf "$PROJ/skills"

echo "=== F-06: orphan still detected when the sentinel is lost ==="
reset

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
95% 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).

Content

Scanner excerpt · tests/test_session_tracker_v26.sh (reported line 117)May include surrounding context.

sh
reset
$ST init "session one" >/dev/null 2>&1; $ST done >/dev/null 2>&1
$ST init "session two" --steps "a" >/dev/null 2>&1
rm -f "$SESSION_TRACKER_DIR/SESSION_ACTIVE"
OUT=$($ST crash-detect 2>&1)
check "crash detected despite older session_done in worklog" "META-CRASH DETECTED" "$OUT"

Session Persistence

Medium
Category
Rogue Agent
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.

Content

Scanner excerpt · PROVENANCE.md (reported line 56)May include surrounding context.

md
reproduced against the v2.5 code before being fixed. Full table in
  references/SECURITY.md.
  * F-01 (Critical): save_state was a non-atomic in-place rewrite. A crash
    mid-write left torn JSON, load_state returned None, and crash-detect fell
    back to "Task: <unknown>" with no steps while resume failed outright — the
    tool's one purpose failing in its one scenario. Now atomic writes plus a
    .bak copy plus reconstruction from the append-only worklog.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

L05 states that v2.5 'closes' the highest-severity findings, but the later review in L42-L64 says additional critical/high issues were reproduced in the shipped v2.5 code. Because both statements are in the same file and concern the same released version, the earlier claim overstates the actual security posture and contradicts the later evidence.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

Lines L33-L35 assert that no suspicious patterns were detected and present the implementation as benign, yet lines L42-L64 explicitly state that numerous serious behaviors were reproduced against the shipped v2.5 code. This is not merely incomplete documentation; it actively gives a contradictory security impression about the codebase under review.

Content

No source excerpt is available for this finding.

Unbounded Output

Medium
Category
Output Handling
Confidence
75% confidence
Finding

Output size or generation rate is not bounded. Unbounded output enables denial-of-service through resource exhaustion, log flooding, or context-window stuffing.

Content

Scanner excerpt · PROVENANCE.md (reported line 77)May include surrounding context.

md
| **F-07** | Medium | Aborted `init` exited 0, so `init … && do_work` proceeded as though tracking had started — the one case where the safety net silently is not there. | Exits 3 on orphan abort; `cleanup` guard aborts exit 2 |
| **F-08** | Low | Every aborted `init` wrote another `crashed_state_*.json`; an agent retrying in a loop filled the session dir. | Archives at most once per orphan |
| **F-09** | Low | v2.5 changed `monitor.pid` to JSON but left `cmd_status` reading it as a bare string, printing `Monitor: PID {"pid": 42, …}`. | `cmd_status` uses `_read_monitor_pid_record` + `_is_our_monitor` |
| **F-10** | Low | `worklog.jsonl` had no size cap, and `crash-detect`/`doctor`/`stats` read it whole on every call. | Rotates to `worklog.jsonl.1` past 5 MB |
| **F-11** | Low | Frontmatter `description` was 1,414 chars against the documented 1,024 limit; the overflow was the disclosure text the Tp4 finding relies on, so truncation would cut exactly the part counted as the fix. Body was ~6.5k tokens against the ~5k guideline. | Description trimmed; audit tables, changelog and research notes moved to `references/` (third disclosure level) |
| **F-13** | Low | `sync` gated on `sys.stdin.isatty()`; agent shells are non-tty even with no pipe, so a bare `sync` reported "invalid TodoWrite JSON". | Empty stdin prints the stored todo list; non-array input rejected with a clear message |

Unbounded Output

Medium
Category
Output Handling
Confidence
75% confidence
Finding

Output size or generation rate is not bounded. Unbounded output enables denial-of-service through resource exhaustion, log flooding, or context-window stuffing.

Content

Scanner excerpt · references/SECURITY.md (reported line 63)May include surrounding context.

md
| **F-07** | Medium | Aborted `init` exited 0, so `init … && do_work` proceeded as though tracking had started — the one case where the safety net silently is not there. | Exits 3 on orphan abort; `cleanup` guard aborts exit 2 |
| **F-08** | Low | Every aborted `init` wrote another `crashed_state_*.json`; an agent retrying in a loop filled the session dir. | Archives at most once per orphan |
| **F-09** | Low | v2.5 changed `monitor.pid` to JSON but left `cmd_status` reading it as a bare string, printing `Monitor: PID {"pid": 42, …}`. | `cmd_status` uses `_read_monitor_pid_record` + `_is_our_monitor` |
| **F-10** | Low | `worklog.jsonl` had no size cap, and `crash-detect`/`doctor`/`stats` read it whole on every call. | Rotates to `worklog.jsonl.1` past 5 MB |
| **F-11** | Low | Frontmatter `description` was 1,414 chars against the documented 1,024 limit; the overflow was the disclosure text the Tp4 finding relies on, so truncation would cut exactly the part counted as the fix. Body was ~6.5k tokens against the ~5k guideline. | Description trimmed; audit tables, changelog and research notes moved to `references/` (third disclosure level) |
| **F-13** | Low | `sync` gated on `sys.stdin.isatty()`; agent shells are non-tty even with no pipe, so a bare `sync` reported "invalid TodoWrite JSON". | Empty stdin prints the stored todo list; non-array input rejected with a clear message |

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The top-level documentation explicitly states 'No file-content reading', which contradicts the implementation. The script opens and reads many files' contents, such as state/worklog/journal files and Linux /proc data, so this is an active intent/documentation mismatch rather than mere incompleteness.

Content

No source excerpt is available for this finding.

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

Medium
Category
Data Flow
Confidence
80% confidence
Finding

SNAP_PREV is created from a path derived from SESSION_TRACKER_DIR, and the code writes it directly during scan baseline creation. This can be redirected to arbitrary writable locations if the session directory is untrusted or symlink-manipulated.

Content

Scanner excerpt · scripts/session_tracker.py (reported line 314)May include surrounding context.

python
except ValueError:
            prev = None
    if prev is None:
        with open(SNAP_PREV, "w") as f:
            json.dump(cur, f)
        print(f"Baseline snapshot taken: {len(cur)} files tracked under SCAN_DIRS.")
        print("Run `scan` again after some activity to see the diff.")

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

Medium
Category
Data Flow
Confidence
80% confidence
Finding

The subsequent overwrite of SNAP_PREV during scan updates has the same path-trust problem as its initial creation. Repeated writes may continuously clobber an unintended target if the derived path has been redirected.

Content

Scanner excerpt · scripts/session_tracker.py (reported line 337)May include surrounding context.

python
print(f"  ~ {p}")
        for p in read_[:10]:
            print(f"  r {p}")
        with open(SNAP_PREV, "w") as f:
            json.dump(cur, f)
    # v2.6.1 (superz amendment): no worklog append. `scan` is a read-only
    # probe, but wl() appends a line to worklog.jsonl — and the monitor's

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

Medium
Category
Data Flow
Confidence
86% confidence
Finding

WORKLOG is derived from SESSION_TRACKER_DIR, which is attacker-controllable via the environment, and wl() appends to that path without verifying the target remains inside an owned session directory. An attacker who can influence the environment or replace files with symlinks could redirect writes to arbitrary filesystem locations accessible to the running user, causing file clobbering or unauthorized file creation.

Content

Scanner excerpt · scripts/session_tracker.py (reported line 362)May include surrounding context.

python
pass
    entry = dict(entry)
    entry.setdefault("ts", now_iso())
    with open(WORKLOG, "a") as f:
        f.write(json.dumps(entry, ensure_ascii=False) + "\n")
        f.flush()
        os.fsync(f.fileno())  # the worklog is the rebuild source — make it durable

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

Medium
Category
Data Flow
Confidence
86% confidence
Finding

JOURNAL is also derived from the environment-controlled session directory and is opened for append with no containment check at the point of use. In an agent setting where skills may be run with inherited environment variables, this can be abused to write structured data to unintended files or to follow symlinks into sensitive user-owned paths.

Content

Scanner excerpt · scripts/session_tracker.py (reported line 373)May include surrounding context.

python
os.makedirs(SESSION_DIR, exist_ok=True)
    entry = dict(entry)
    entry.setdefault("ts", now_iso())
    with open(JOURNAL, "a") as f:
        f.write(json.dumps(entry, ensure_ascii=False) + "\n")

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

Medium
Category
Data Flow
Confidence
84% confidence
Finding

NOTICE is written based on SESSION_TRACKER_DIR-derived paths without ensuring the resolved target is still under the expected owned directory. While the content is not highly sensitive, arbitrary file overwrite within the executing user's permissions is still possible if the session directory or file path is manipulated.

Content

Scanner excerpt · scripts/session_tracker.py (reported line 562)May include surrounding context.

python
lines += ["", "Full archived state: see crashed_state_*.json in this directory."]
    lines += ["Run `crash-detect` for the full report, or `resume` for the resume plan."]
    os.makedirs(SESSION_DIR, exist_ok=True)
    with open(NOTICE, "w") as f:
        f.write("\n".join(lines) + "\n")

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

Medium
Category
Data Flow
Confidence
84% confidence
Finding

The archived orphan-state path is computed under SESSION_DIR and then opened directly for writing. If SESSION_DIR is attacker-influenced or contains malicious symlinks, the archive operation can create or overwrite files outside the intended session area.

Content

Scanner excerpt · scripts/session_tracker.py (reported line 580)May include surrounding context.

python
ts_tag = now_iso().replace(":", "").replace("-", "")[:15]  # YYYYMMDDTHHMMSS
    path = os.path.join(SESSION_DIR, f"crashed_state_{ts_tag}.json")
    os.makedirs(SESSION_DIR, exist_ok=True)
    with open(path, "w") as f:
        json.dump(st, f, indent=1, ensure_ascii=False)
    entry = {
        "event": "session_orphaned", "task": st.get("task"),

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

Medium
Category
Data Flow
Confidence
86% confidence
Finding

TODO is written under a directory ultimately controlled by an environment variable, and this write path lacks final containment or symlink checks. In practice, that enables arbitrary file creation or overwrite as the current user if a hostile caller controls SESSION_TRACKER_DIR or the filesystem layout under it.

Content

Scanner excerpt · scripts/session_tracker.py (reported line 651)May include surrounding context.

python
"version": "2.6.1-local",
    }
    save_state(st)
    with open(TODO, "w") as f:
        json.dump([{"id": str(s["n"]), "content": s["desc"],
                    "status": s["status"]} for s in steps], f, indent=1)
    os.makedirs(SESSION_DIR, exist_ok=True)

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

Medium
Category
Data Flow
Confidence
82% confidence
Finding

SENTINEL is written via a direct open() on a path derived from SESSION_TRACKER_DIR, again without final path verification. Although the file content is simple, the ability to create or overwrite arbitrary writable files is still a real integrity issue in a multi-skill or user-influenced environment.

Content

Scanner excerpt · scripts/session_tracker.py (reported line 662)May include surrounding context.

python
_atomic_write(MARKER, json.dumps(
        {"created": now_iso(), "version": "2.6.1",
         "note": "Directory owned by session-tracker; cleanup may delete it."}))
    open(SENTINEL, "w").write(now_iso() + "\n")
    if a.fs_scan:
        with open(SNAP_PREV, "w") as f:
            json.dump(fs_snapshot(), f)

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

Medium
Category
Data Flow
Confidence
80% confidence
Finding

This fs-scan initialization writes SNAP_PREV using the same untrusted derived path model. The repeated pattern across session artifacts increases confidence that arbitrary file-write redirection is systemic rather than isolated.

Content

Scanner excerpt · scripts/session_tracker.py (reported line 664)May include surrounding context.

python
"note": "Directory owned by session-tracker; cleanup may delete it."}))
    open(SENTINEL, "w").write(now_iso() + "\n")
    if a.fs_scan:
        with open(SNAP_PREV, "w") as f:
            json.dump(fs_snapshot(), f)
    wl({"event": "init", "task": a.task, "steps": len(steps),
        "fs_scan": bool(a.fs_scan), "auto_cleanup": bool(a.auto_cleanup),

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

Medium
Category
Data Flow
Confidence
86% confidence
Finding

The sync command writes TODO from stdin-derived content to a path controlled by SESSION_TRACKER_DIR. Combined with path redirection, this allows an attacker to place attacker-controlled JSON into arbitrary writable files, which is more dangerous than fixed-content writes because the attacker controls both destination and payload.

Content

Scanner excerpt · scripts/session_tracker.py (reported line 776)May include surrounding context.

python
entry["n"] = int(sid) if sid.isdigit() else len(st.get("steps", [])) + 1
                st.setdefault("steps", []).append(entry)
        save_state(st)
        with open(TODO, "w") as f:
            json.dump(items, f, indent=1)
        wl({"event": "sync", "items": len(items)})
        print(f"Synced {len(items)} todo items — tracker is source of truth; sync adds/updates only.")

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

Medium
Category
Data Flow
Confidence
86% confidence
Finding

MONITOR_LOG is opened for append using a path derived from the environment-controlled session directory. Because monitor startup is reachable through a normal command path, a malicious environment or symlink swap could redirect monitor output into unintended files and repeatedly append data there.

Content

Scanner excerpt · scripts/session_tracker.py (reported line 1156)May include surrounding context.

python
env = {k: os.environ[k] for k in ENV_ALLOWLIST if k in os.environ}
    env["SESSION_TRACKER_SELF"] = os.path.abspath(__file__)
    env["SESSION_TRACKER_MODE"] = "detached"
    with open(MONITOR_LOG, "a") as lf:
        p = subprocess.Popen(
            [sys.executable, os.path.abspath(__file__),
             "monitor", "--foreground", "--interval", str(interval)],

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/session_tracker.py (reported line 1157)May include surrounding context.

python
env["SESSION_TRACKER_SELF"] = os.path.abspath(__file__)
    env["SESSION_TRACKER_MODE"] = "detached"
    with open(MONITOR_LOG, "a") as lf:
        p = subprocess.Popen(
            [sys.executable, os.path.abspath(__file__),
             "monitor", "--foreground", "--interval", str(interval)],
            stdout=lf, stderr=lf, env=env, start_new_session=True)

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

Medium
Category
Data Flow
Confidence
86% confidence
Finding

MONITOR_PID is written to a derived path without ensuring the file remains inside the intended owned session directory. If redirected through environment control or a symlink, the program could overwrite another JSON-like file writable by the current user, interfering with unrelated applications or user data.

Content

Scanner excerpt · scripts/session_tracker.py (reported line 1166)May include surrounding context.

python
start_time = _pid_start_time(p.pid)
    pid_record = {"pid": p.pid, "start_time": start_time,
                  "session_id": _SESSION_ID, "started_at": now_iso()}
    with open(MONITOR_PID, "w") as f:
        json.dump(pid_record, f)
    wl({"event": "monitor_start", "pid": p.pid, "interval": interval,
        "start_time": start_time})

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

Medium
Category
Data Flow
Confidence
83% confidence
Finding

After cleanup, the preserved journal is rewritten to JOURNAL using a path derived from the environment-controlled session directory without final target validation. This can still be abused for unintended file writes even though the code has added broader cleanup guards.

Content

Scanner excerpt · scripts/session_tracker.py (reported line 1310)May include surrounding context.

python
shutil.rmtree(SESSION_DIR)
    os.makedirs(SESSION_DIR, exist_ok=True)
    if journal_backup is not None:
        with open(JOURNAL, "w") as f:
            f.write(journal_backup)
        print(f"Journal PRESERVED ({len(journal_backup.strip().splitlines())} entries) — "
              f"use cleanup --purge-journal to delete history too.")

Skill Enumeration

Medium
Category
Agent Snooping
Confidence
80% confidence
Finding

Skill enumerates or reads other installed skills. Access to other skills' SKILL.md files or the skills directory reveals prompt instructions, capabilities, and secrets that should be invisible to peer skills.

Content

Scanner excerpt · tests/test_session_tracker_v26.sh (reported line 104)May include surrounding context.

sh
echo "=== F-05: skills/ is no longer scanned (SKILL.md has claimed this since v2.4) ==="
reset
PROJ="$(dirname "$SESSION_TRACKER_DIR")"
mkdir -p "$PROJ/skills/other-skill"; echo x > "$PROJ/skills/other-skill/SKILL.md"
$ST init "t" --fs-scan >/dev/null 2>&1
if python3 -c "
import json,sys

Static analysis

No suspicious patterns detected.