Back to skill

Security audit

AutomateLab agency-os

Security checks for vulnerabilities and agentic risk

Overview

This is a disclosed Notion task-orchestration skill, but it needs review because it can launch agents from mutable Notion content and treats local credential files too broadly.

Install only if you trust the Notion workspace editors and are comfortable with agents executing approved task content. Use a least-privilege Notion token, remove `.env` from general read/grep guidance, review full task descriptions before `/agency-os run --go`, and require explicit confirmation for writes inferred from natural language.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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)

T01 · Skill Instruction Hijacking

Error
Location
SKILL.md:444
Finding

Untrusted Notion Content Is Treated as Executable Agent Instructions

Content
View full analysis
]() [ / / type={ cadence=}{ last_done=} / effort=] ## Description ## Subtasks (N) - [Status] -> /agency-os start - ... ## Latest discussion entry (of total) (For older entries: /agency-os show --section discussion --entry ) ## Corpus: ## General guidance ``` 6. End with: `Brief loaded. Proceed.` ``` The resulting brief is then used by execution agents as follows: ```text Every spawned agent: 1. Calls `/agency-os start ` to load the kickoff brief AND flip the row To-Do -> In Progress. This MUST be the first call; if the row is already In Progress (re-dispatch), `start` is idempotent and re-emits the brief. 2. **Runnability check:** can this task plausibly be completed end-to-end by an agent, or does it require operator action (logging into a personal account, solving a captcha, clicking publish in a UI without API access)? - If operator-only: call `/agency-os log "blocked-operator: "`, then `/agency-os move --to discussion`, then emit the result report (step 6) with `status: blocked-operator`. Do not skip the status flip. 3. Otherwise: execute the brief end-to-end. 4. **Self-assessment.** Before closing: did I complete 100% of the acceptance criteria? Partial completions are **not** Done. ``` ### Technical Analysis The Skill imports mutable content from Notion into an execution-agent prompt, including the complete task ...[truncated 3116 chars]
Remediation
View remediation
... ``` Instruct the agent to extract requirements from this content but never execute embedded meta-instructions automatically. 4. Before dispatch, display the complete proposed action set rather than only a 200-character preview. Highlight requests involving: - Shell execution. - Network transmission. - Credential or secret access. - Repository writes. - Deployment or publication. - Destructive or irreversible operations. 5. Require separate user confirmation for privileged operations, even after `/agency-os run --go`. 6. Validate and expose the Notion author and last editor for each approved task. Reapproval should be required when executable content changes after approval. 7. Prefer the canonical local General Guidance file over a mutable Notion mirror when constructing execution prompts. 8. Restrict Notion write access and integration permissions to the minimum set of trusted users and databases. 9. Record a hash or revision identifier at approval time and refuse execution if the task content has changed afterward. 10. Give spawned agents narrowly scoped tools and credentials appropriate to each task rather than inheriting broad session permissions. ]]>

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
references/general-guidance.md:54
Finding

Global Guidance Encourages Unrestricted Credential-File Access

Content
View full analysis
## Where information lives The project runs on a **hybrid** local-vs-Notion split. When you need something, read from the right place: - **Local files** (`.claude/skills/`, `docs/`, `.env`, etc.) are source-of-truth for stable spec: skill instructions, project docs, style guides. Free to read, git-tracked. Grep these. - **Notion** is source-of-truth for mutable task state: the rows in the Tasks DB, their statuses, their discussion logs, their done logs, the corpus pages. Read these via the Notion MCP. ``` ### Technical Analysis The guidance groups `.env` with ordinary documentation and labels all such local files as “Free to read” and appropriate to grep. This guidance is included in kickoff briefs and therefore applies broadly to execution tasks, regardless of whether a task legitimately requires a credential. Credential files are not stable specification documents. They commonly contain API keys, database passwords, deployment tokens, webhook secrets, and service credentials. Treating the entire file as generally readable violates least privilege and increases the consequences of prompt injection, mistaken task interpretation, or overly broad exploratory commands. The Python helper has a legitimate need for one specific variable, `NOTION_KEY`, and reads that variable to authenticate to the fixed official Notion endpoint. That narrow use does not justify instructing every execution agent to inspect the complete `.env` file. ### Attack Path 1. An execution task contains a request to inspect configura ...[truncated 1481 chars]
Remediation
View remediation
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (16)

Hidden Instructions

High
Category
Prompt Injection
Confidence
70% confidence
Finding

Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Content

Scanner excerpt · references/general-guidance.md (reported line 54)May include surrounding context.

md
## Project orientation

<!-- TODO: fill in your project root, docs, skills, and credential locations. Example:
- Project root: `C:\Work\my-project`
- Docs: `docs/`
- Skills: `.claude/skills/`

Hidden Instructions

High
Category
Prompt Injection
Confidence
70% confidence
Finding

Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Content

Scanner excerpt · references/task-page-template.md (reported line 1)May include surrounding context.

md
<!--
Rendered into every task page body when created via /agency-os suggest or /agency-os add-subtask.
Toggleable H2 headings (Notion's `is_toggleable: true`) are used so the DB grid stays clean and
the operator opens what they need on click. If the MCP path can't set is_toggleable, fall back

Credential Access

High
Category
Privilege Escalation
Confidence
87% confidence
Finding

The script reads NOTION_KEY from the repository's .env file if it is not present in the process environment. Pulling credentials from a repo-local file increases the chance of accidental disclosure through source control mistakes, overly broad file access by other local tools, or reuse of secrets in untrusted skill contexts.

Content

Scanner excerpt · scripts/query-tasks.py (reported line 29)May include surrounding context.

python
SKILL_DIR = Path(__file__).resolve().parents[1]
POINTERS = SKILL_DIR / "references" / "notion-pointers.json"
SIDECAR = SKILL_DIR / "state" / "todo-ids.json"
ENV_FILE = REPO_ROOT / ".env"


def load_env_var(name: str) -> str:

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 · SKILL.md (reported line 31)May include surrounding context.

description: "Run /agency-os ", subagent_type: "general-purpose", model: "sonnet", prompt: "Run the agency-os skill for: /agency-os .\n\nUse medium reasoning effort — think through ID resolution, dedup checks, and brief assembly carefully, but don't over-deliberate on mechanical mutations.\n\nRead .claude/skills/agency-os/SKILL.md and execute that exact command end-to-end: sync preflight (call notion-fetch live — never read from any local cache file), resolve IDs against the live Notion result, mutate Notion via the Notion MCP, and return the same output format the skill specifies (the brief for start, the + Suggestion: ... -> url line for suggest, etc.). All task/result links must be formatted as CommonMark markdown links — [title](url) — never HTML <a> tags and never a bare URL. Claude Code renders markdown but not HTML, so HTML anchors show up as literal text. If the command is start, also emit the full kickoff brief verbatim. If anything fails (sync, MCP call, ID resolution), stop and report — do not guess.\n\nYOU MUST ALWAYS PRODUCE OUTPUT. Never return silently. On success: the skill's standard output. On failure: one paragraph describing exactly what failed, what was attempted, and what state Notion was left in. Returning nothing is not an option." })

text

Skill Enumeration

Medium
Category
Agent Snooping
Confidence
85% 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 · SKILL.md (reported line 51)May include surrounding context.

md
On Cursor, Cline, Continue, and generic MCP harnesses, the skill can't spawn subagents. Instead:

1. **Before first use:** run `/agency-os init` to store your preferred models in `.claude/skills/agency-os/config.json`.
2. **Mutations (suggest, discuss, log, etc.):** run inline on the main agent.
3. **Batch execution (`/agency-os run`):** read `config.json` and use the stored models' constraints when evaluating task complexity.

Skill Enumeration

Medium
Category
Agent Snooping
Confidence
85% 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 · SKILL.md (reported line 79)May include surrounding context.

  • In Notion, source-of-truth: task rows (Title / Status / Corpus / Priority / Impact / Type / Cadence / Effort / Parent Task), discussion logs, done logs, corpus pages (Goal + local guidance the operator authors), and the Hub itself with its DB views. Anything the operator wants to review-in-place, anything that changes frequently, anything that doesn't benefit from git log.
  • One-way mirror, not two-way: references/general-guidance.md -> Notion General Guidance page is the only sync direction. Edit the local file, then push to Notion (via scaffold or a manual update). Never edit the Notion page and try to reverse-sync it; drift will follow.

When this skill assembles a brief for an agent, it pulls task state from Notion (the row, the discussion log, subtask titles) and stable spec from local files (general guidance, corpus local guidance if those are mirrored, links into docs/ and .claude/skills/). The brief contains pointers to local files, not copies of them, so the agent can grep further when it needs to.

text
# setup

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The skill allows broad natural-language triggers for state-changing actions, which creates ambiguity about when a user intended to mutate Notion versus merely discuss options. In chat-based environments, that can cause unintended task creation, status changes, or workflow launches from conversational phrasing or prompt-injected text embedded in task content.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The spec explicitly permits Notion mutations from natural language and only inconsistently requires confirmation, allowing some appends and subtask creation 'without asking each time.' That weak consent model can let the agent perform unauthorized state changes, especially when handling long conversations, pasted transcripts, or adversarial content that looks like user intent.

Content

No source excerpt is available for this finding.

Skill Enumeration

Medium
Category
Agent Snooping
Confidence
85% 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 · SKILL.md (reported line 281)May include surrounding context.

Non-Claude harnesses only. Configure which models to use for task execution. Claude Code harnesses ignore this; they have built-in model selectors.

Why it matters: Cursor, Cline, Continue, and generic MCP harnesses can't spawn subagents with different models on the fly. Instead, store your preferred models upfront in .claude/skills/agency-os/config.json, then the skill uses them during batch execution.

Interactive mode (recommended):

text

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
90% confidence
Finding

The spec allows the agent to make autonomous write decisions for 'trivial' appends and subtask creation without asking each time. That opens the door to unauthorized or manipulated mutations, because the agent is trusted to infer intent from conversation context and can be steered by ambiguous language or adversarial inserted text.

Content

Scanner excerpt · SKILL.md (reported line 784)May include surrounding context.

md
| "show me X" / "details on X" | `show <id>` |
| "what's the status" / "where are we" | `status` |

**The skill is the user's hands.** When in doubt, the agent asks: "Should I `<command equivalent>` for that?" and only mutates after confirmation. But trivial appends (a `log` entry during an open `discuss` session, an `add-subtask` from an explicit "we'll need to" moment) can be done without asking each time.

---

Autonomous Decision Making

Medium
Category
Excessive Agency
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.

Content

Scanner excerpt · SKILL.md (reported line 814)May include surrounding context.

md
## What this skill does NOT do

- **Does not auto-execute To-Do items.** `next` shows; only `start` (after explicit invocation) loads the brief and flips status.
- **Does not write content, deploy, or run any other skill.** It mutates Notion. The brief may **point** an agent at another skill, but this skill never invokes one.
- **Does not delete content.** `kill` is terminal but archival; the row remains in Notion. To truly remove, archive in Notion manually.
- **Does not commit or push to git** beyond `notion-pointers.json` (which scaffold writes).

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The 'What this skill does NOT do' section states 'Does not auto-execute To-Do items' and contrasts next with explicit start. Earlier sections, however, define /agency-os run [--go] as batch execution that auto-refreshes, plans, dispatches execution agents, and has each spawned agent call start <id> automatically. That is an active contradiction in the documentation about whether queued To-Do work can be auto-executed.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The guidance says the skill responds to broad natural-language phrases like "add a suggestion," "approve it," and "X is done" without defining confirmation, authorization, or disambiguation requirements. In an agent skill that mutates task state, this can allow unintended or prompt-injected instructions to trigger state-changing actions, especially when language is ambiguous or references are indirect.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/query-tasks.py (reported line 111)May include surrounding context.

python
"""Fetch a single page and read its Status property. Used to resolve dep statuses
    for tasks that aren't in the in-batch (To-Do AND Exec=Agent) result set."""
    req = urllib.request.Request(
        f"https://api.notion.com/v1/pages/{page_id}",
        headers={
            "Authorization": f"Bearer {token}",
            "Notion-Version": NOTION_API_VERSION,

External Transmission

Medium
Category
Data Exfiltration
Confidence
78% confidence
Finding

This code fetches block children from Notion and extracts a description preview, then writes that content into a local sidecar file. Although the destination is the legitimate Notion API, the additional retrieval of freeform page content increases data exposure beyond simple task enumeration and could pull sensitive text from task descriptions into local state where other tools or agents may access it.

Content

Scanner excerpt · scripts/query-tasks.py (reported line 131)May include surrounding context.

python
H2 and BEFORE the next H2. Plain H2 + sibling paragraphs is the layout we observe;
    is_toggleable doesn't matter here. Best-effort; empty string on miss."""
    req = urllib.request.Request(
        f"https://api.notion.com/v1/blocks/{page_id}/children?page_size=50",
        headers={
            "Authorization": f"Bearer {token}",
            "Notion-Version": NOTION_API_VERSION,

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
87% confidence
Finding

The documentation says the skill 'does not ... run any other skill' and 'it mutates Notion.' But the run workflow explicitly spawns execution agents that must call /agency-os start, /agency-os done, /agency-os move, and /agency-os log during task execution. While these are self-invocations rather than unrelated plugins, the statement actively misdescribes the implementation by implying the skill only mutates Notion and does not drive command execution.

Content

No source excerpt is available for this finding.