Back to skill

Security audit

sn-work-record

Security checks for vulnerabilities and agentic risk

Overview

ClawScan’s verdict is benign with medium confidence: the scans report mostly purpose-aligned behavior, but flag deletion and restoration capabilities that conflict with the stated disabled status, plus an unpinned release-tool command.

The verdict is benign, not a guarantee of zero risk, and its confidence is medium. Before installing, weigh the mismatch around deletion and restoration and the unpinned release-tool command; the scanner data also records a Skillspector exit-status error, so treat the assessment as having some uncertainty.

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
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Output HandlingUnvalidated Output Injection, Cross-Context Output, Unbounded Output
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (17)

Description-Behavior Mismatch

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The function protected_change actively implements both delete and restore operations. Confirmation, locking, journaling, and readback checks constrain these operations but do not disable them. Although the module docstring describes an offline experimental workflow not connected to production, the function accepts arbitrary read/write callbacks and does not enforce an offline-only restriction. This chunk does not establish production integration or directly issue deletion requests, but it does expose operational deletion/restoration behavior contrary to the declared prohibition.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

High
Category
MCP Tool Poisoning
Confidence
92% confidence
Finding

The successful-path test invokes protected_change with action='delete' and then action='restore', verifies both operations, and checks that the writer receives [1, 0]. This indicates supported deletion and restoration workflows, not unconditional prohibition. Confirmation and backup safeguards restrict these operations but do not disable them. Because only tests are supplied, this establishes a mismatch in the behavior the tests exercise and expect; it does not prove that live deletion or restoration is reachable through the skill's user-facing entry points.

Content

No source excerpt is available for this finding.

os.system() or os exec-family call

High
Category
Dangerous Code Execution
Confidence
85% confidence
Finding

os.system() and os exec-family calls run shell commands with the process's full privileges, enabling arbitrary command execution.

Content

Scanner excerpt · scripts/runtime_bootstrap.py (reported line 91)May include surrounding context.

python
if _has_modules(candidate, required_modules):
            new_env = runtime_environment(credentials=True)
            new_env[ENV_GUARD] = "1"
            os.execve(candidate, [candidate, *sys.argv], new_env)

    missing = ", ".join(required_modules)
    raise RuntimeError(

Skill declares no tool scope ('permissions' or 'allowed-tools') but code capabilities were detected: env, file_read, file_write, network, shell.

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding

Without declared permissions the skill's intent is opaque and cannot be validated.

Content

No source excerpt is available for this finding.

MCP server referenced without pinned version: 'npx -y clawhub'.

Medium
Category
MCP Rug Pull
Confidence
96% confidence
Finding

Although clawhub is a CLI rather than an MCP server here, npx -y clawhub can download and execute an unpinned package without an installation confirmation. A compromised future package or dependency could execute with the maintainer's filesystem and environment access, potentially exposing OA credentials or publishing credentials.

Content

No source excerpt is available for this finding.

MCP server referenced without pinned version: 'npx -y clawhub'.

Medium
Category
MCP Rug Pull
Confidence
96% confidence
Finding

The dry-run command still executes an automatically resolved, unpinned npm package; dry-run limits the intended publishing operation, not arbitrary package behavior. Compromised release tooling could read local secrets or modify the staged skill before publication.

Content

No source excerpt is available for this finding.

MCP server referenced without pinned version: 'npx -y clawhub'.

Medium
Category
MCP Rug Pull
Confidence
96% confidence
Finding

Pinning the inspected skill version does not pin the clawhub executable fetched by npx. Even this nominally read-only verification command can execute compromised package code with local access to sensitive OA or release assets.

Content

No source excerpt is available for this finding.

Unsafe Defaults

Medium
Category
Tool Misuse
Confidence
75% confidence
Finding

Tool defaults are unsafe or overly permissive (e.g. disabled TLS verification, no authentication, world-writable permissions). Unsafe defaults widen the attack surface.

Content

Scanner excerpt · references/security-revision-1.15.1.md (reported line 15)May include surrounding context.

md
|6|TT2 / 同上|第5项同源重复,不是额外shell注入;同一修复覆盖。|
|7|AST3 / oa_utils.py __import__('sys')|常量标准库 sys,非动态不可信模块。已在文件顶部普通 import sys,现有写法冗余但无已证实风险;不为迎合规则改写或隐藏。|
|8|AST4 / runtime_bootstrap.py subprocess|固定 Python 探测代码、显式 argv、shell=False 默认,模块名作为数据传入,不拼接成代码。没有证实命令注入;实质问题是原先继承环境,已按第2项修复。|
|9|TM3 / tests/test_query_time_ranges.py|误报:`Auth=no` 是 `OAAuth=noisy` 子串,noisy 是离线测试中故意抛异常以验证脱敏的 fake。没有关闭认证/TLS或宽权限默认;保持测试不变。|
|10|SQP-3 / references/write-workflow.md|文档中文固定回复可改为按用户语言翻译示例,但属于输出政策范围外实质变更,暂不改,交父agent确认;与凭据外泄或删除安全无关。|
|11|AST7 / configure_personal_settings.py|getattr 目标由固定五字段元组限定,不接受任意属性名,不存在报告所称绕过访问控制证据。保持不变。|
|12|AST7 / submit_time_entry.py|getattr 目标由固定四字段元组限定,仅读取 argparse 选项;没有任意属性访问。保持不变。|

Unsafe Defaults

Medium
Category
Tool Misuse
Confidence
75% confidence
Finding

Tool defaults are unsafe or overly permissive (e.g. disabled TLS verification, no authentication, world-writable permissions). Unsafe defaults widen the attack surface.

Content

Scanner excerpt · references/security-revision-1.15.1.md (reported line 15)May include surrounding context.

md
|6|TT2 / 同上|第5项同源重复,不是额外shell注入;同一修复覆盖。|
|7|AST3 / oa_utils.py __import__('sys')|常量标准库 sys,非动态不可信模块。已在文件顶部普通 import sys,现有写法冗余但无已证实风险;不为迎合规则改写或隐藏。|
|8|AST4 / runtime_bootstrap.py subprocess|固定 Python 探测代码、显式 argv、shell=False 默认,模块名作为数据传入,不拼接成代码。没有证实命令注入;实质问题是原先继承环境,已按第2项修复。|
|9|TM3 / tests/test_query_time_ranges.py|误报:`Auth=no` 是 `OAAuth=noisy` 子串,noisy 是离线测试中故意抛异常以验证脱敏的 fake。没有关闭认证/TLS或宽权限默认;保持测试不变。|
|10|SQP-3 / references/write-workflow.md|文档中文固定回复可改为按用户语言翻译示例,但属于输出政策范围外实质变更,暂不改,交父agent确认;与凭据外泄或删除安全无关。|
|11|AST7 / configure_personal_settings.py|getattr 目标由固定五字段元组限定,不接受任意属性名,不存在报告所称绕过访问控制证据。保持不变。|
|12|AST7 / submit_time_entry.py|getattr 目标由固定四字段元组限定,仅读取 argparse 选项;没有任意属性访问。保持不变。|

Unsafe Defaults

Medium
Category
Tool Misuse
Confidence
75% confidence
Finding

Tool defaults are unsafe or overly permissive (e.g. disabled TLS verification, no authentication, world-writable permissions). Unsafe defaults widen the attack surface.

Content

Scanner excerpt · tests/test_query_time_ranges.py (reported line 154)May include surrounding context.

python
print('raw-body', file=sys.stderr)
            raise RuntimeError('pass-secret')
        api = SimpleNamespace(load_credentials=lambda path: ('user-secret', 'pass-secret', 'url'),
                              resolve_base_url=lambda base, override: base, OAAuth=noisy)
        out, err = io.StringIO(), io.StringIO()
        with patch.object(mod, '_api', return_value=api), contextlib.redirect_stdout(out), contextlib.redirect_stderr(err):
            rc = mod.main(['--range', '2026-10-08', '2026-10-09', '--json'])

The workflow mandates a Chinese-language holiday reply without offering a user language choice.

Medium
Category
Output Handling
Confidence
91% confidence
Finding

The instruction requires the exact reply “节假日期间无需填写工时~”, regardless of the user's language. Although the regional holiday restrictions have a documented context, that does not require the user-facing explanation to be in Chinese.

Content

No source excerpt is available for this finding.

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/oa_utils.py (reported line 97)May include surrounding context.

python
os.write(write_fd, encoded + b'\n')
        os.close(write_fd)
        write_fd = None
        result = subprocess.run(
            ["openssl", "aes-256-cbc", mode, "-a", "-pbkdf2", "-pass", f"fd:{read_fd}"],
            input=data, capture_output=True, timeout=10,
            pass_fds=(read_fd,), env=runtime_environment(),

Dynamic import via __import__()

Medium
Category
Dangerous Code Execution
Confidence
75% confidence
Finding

Dynamic import() can load arbitrary modules at runtime, bypassing static analysis and potentially importing malicious code.

Content

Scanner excerpt · scripts/oa_utils.py (reported line 214)May include surrounding context.

python
for name, src in candidates:
        result = _try(src)
        if result is not None:
            print(f"  [captcha] {name} → '{ocr.classification(src)}' → {result}", file=__import__("sys").stderr)
            return result
    return None

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/runtime_bootstrap.py (reported line 61)May include surrounding context.

python
"raise SystemExit(0 if not missing else 1)"
    )
    try:
        proc = subprocess.run(
            [python_executable, "-c", code, *modules],
            stdout=subprocess.DEVNULL,
            stderr=subprocess.DEVNULL,

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Confidence
50% confidence
Finding

Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Content

Scanner excerpt · scripts/configure_personal_settings.py (reported line 33)May include surrounding context.

python
host = resolve_base_url(host, args.base_url)
        auth = OAAuth(host, account, password)
        catalog = auth.call(lambda session: discover_options(session, host))
        values = resolve_selection(catalog, {field: getattr(args, field) for field in ('project', 'man_hour', 'fill_type', 'project_phase', 'work_type')})
        if args.confirm_save:
            save_settings(args.settings, account, host, values, confirmed=True)
    except (ValueError, OSError):

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Confidence
50% confidence
Finding

Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Content

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

python
if path.is_symlink():
        raise ValueError('个人设置不得为符号链接')
    defaults = resolve_settings(path, account, host, {}) if path.exists() else {}
    choices = {field: getattr(args, field) if getattr(args, field) is not None else defaults.get(field)
               for field in ('fill_type', 'project_phase', 'work_type', 'man_hour')}
    selectors = [value for value in (args.project, args.project_id) if value is not None]
    if len(selectors) > 1:

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Confidence
50% confidence
Finding

Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Content

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

python
if path.is_symlink():
        raise ValueError('个人设置不得为符号链接')
    defaults = resolve_settings(path, account, host, {}) if path.exists() else {}
    choices = {field: getattr(args, field) if getattr(args, field) is not None else defaults.get(field)
               for field in ('fill_type', 'project_phase', 'work_type', 'man_hour')}
    selectors = [value for value in (args.project, args.project_id) if value is not None]
    if len(selectors) > 1: