Back to skill

Security audit

Nota Sign

Security checks for vulnerabilities and agentic risk

Overview

This Nota Sign skill is purpose-aligned, but needs review because it stores a long-lived private key in plaintext and relies on unpinned npm tooling while handling documents and signer data.

Review before installing. Use only with documents you are authorized to send to Nota Sign, prefer a pinned dependency workflow instead of live npx downloads, store credentials only in the home config with restrictive permissions, avoid project-local config files, and do not submit arbitrary internal or private-network URLs as document inputs.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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 (3)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/send_envelope.ts:525
Finding
Nota Sign private key is stored without enforced restrictive permissions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/send_envelope.ts:474-475, 525-526` **Vulnerability Type**: Insecure secret input and storage **Risk Level**: High ### Complete Code Snippet ```typescript console.log('App Key (Base64 encoded PKCS#8 private key):'); const appKey = await prompt('> '); if (!appKey) { return { success: false, message: 'App Key is required' }; } ``` ```typescript // Write config file try { await fs.writeFile(configPath, JSON.stringify(config, null, 2), 'utf8'); return { success: true, message: 'Configuration saved successfully', configPath }; } catch (error) { return { success: false, message: `Failed to write config file: ${error}` }; } ``` The configuration object written here contains `appId`, `appKey`, `userCode`, `serverRegion`, and `environment`. The `appKey` is a Base64-encoded PKCS#8 RSA private key. ### Technical Analysis The script writes a long-lived authentication private key to a plaintext JSON file without explicitly applying a restrictive file mode. The resulting permissions depend on the process umask and any permissions on an existing file. Under a permissive environment, another local account could read the key. The README recommends manually running `chmod 600`, but the initialization implementation does not enforce this control. The private key is also collected through the ordinary `prompt` function, which reads from standard input without disabling terminal echo. Consequently, the key can be exposed on screen, in terminal recordings, or to shoulder surfing. The private key must remain locally available because it is required to sign JWT and API requests. Persisting it is therefore related to the declared functionality, but storing it without enforced access controls exceeds safe minimum-secret-handling requirements. ### Attack Path 1. A user runs `scripts/send_envelope.ts init`. 2. The user enters the Nota Sign PKCS#8 private key into an echoed terminal prompt. 3. The ...[truncated 1112 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Create `~/.notasign` with mode `0700`. 2. Create the configuration file atomically with mode `0600`, for example by using an exclusive temporary file in the destination directory, applying `chmod`, and then renaming it. 3. Explicitly call `chmod(configPath, 0o600)` after writing, including when replacing an existing file. 4. Before loading an existing configuration, inspect its owner and permission bits. Reject or repair files accessible by group or other users. 5. Disable terminal echo while collecting the private key, and restore terminal state in a `finally` block. 6. Prefer an operating-system credential store or secrets manager over a plaintext JSON file where available. 7. Never include the private key in logs, returned result objects, exception details, or command-line arguments. ]]>

T08 · Insecure Dependencies

Error
Location
SKILL.md:72
Finding
Unpinned packages are downloaded and executed through npx<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:72-83` **Vulnerability Type**: Unsafe runtime dependency retrieval **Risk Level**: High ### Complete Code Snippet ```bash npx tsx scripts/send_envelope.ts --file "PATH_OR_URL" --signers '[{"userName":"Alice","userEmail":"alice@example.com"}]' --subject "Optional subject" ``` ```bash npx -y -p node@20 -p tsx -c 'tsx scripts/send_envelope.ts --file "PATH_OR_URL" --signers '"'"'[{"userName":"Alice","userEmail":"alice@example.com"}]'"'"' --subject "Optional subject"' ``` The same unpinned fallback is also documented for initialization: ```bash npx -y -p node@20 -p tsx -c 'tsx scripts/send_envelope.ts init' ``` ### Technical Analysis The operational instructions invoke `tsx` without an exact version and permit `npx` to download and immediately execute it. The fallback specifies only the major version of Node.js and leaves `tsx` entirely unversioned. The project contains no package manifest or lockfile establishing reviewed versions or integrity values. This makes the code executed during each run dependent on mutable npm registry state rather than solely on the audited Skill package. A compromised maintainer account, malicious release, registry compromise, or unexpected upstream change could introduce arbitrary code after this Skill has been reviewed. This is particularly sensitive because the downloaded code runs with the user's privileges and in a process that can access local documents and the Nota Sign private key. Although obtaining a compatible TypeScript runtime is necessary, fetching an unpinned executable package during credential initialization or document transmission is not the least-risk approach. ### Attack Path 1. The local machine lacks a suitable preinstalled runtime, or `tsx` is not locally installed. 2. The user or Agent follows the documented `npx` command. 3. `npx` resolves the current package versions from the configured npm registry and downloads them. 4. A compromised o ...[truncated 984 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Add a `package.json` and lockfile containing exact, reviewed dependency versions. 2. Install dependencies ahead of execution with a lockfile-enforcing command such as `npm ci`. 3. Pin Node.js and `tsx` to exact versions instead of `node@20` and an unversioned `tsx`. 4. Verify package integrity and use an explicitly trusted registry. 5. Avoid downloading executable dependencies during credential initialization or document processing. 6. If runtime bootstrapping remains necessary, obtain a verified artifact from a controlled source and validate its cryptographic checksum or signature before execution. 7. Document the supply-chain risk and require explicit user approval before any network-based runtime installation. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/send_envelope.ts:65
Finding
Arbitrary remote document URLs are delegated for server-side retrieval<![CDATA[ ## Vulnerability Details **File Location**: `scripts/send_envelope.ts:65-71, 391-399` **Vulnerability Type**: Insufficient URL validation and potential server-side request forgery **Risk Level**: Medium ### Complete Code Snippet ```typescript async function validateInputFile(filePath: string): Promise<void> { const isUrl = filePath.startsWith('http://') || filePath.startsWith('https://'); if (isUrl) { const fileName = path.basename(new URL(filePath).pathname); assertSupportedFileName(fileName); return; } ``` ```typescript async function sendDocumentForSigning( filePath: string, signers: Array<{ userName: string; userEmail: string }>, subject?: string ): Promise<string> { const isUrl = filePath.startsWith('http://') || filePath.startsWith('https://'); const fileId = isUrl ? await convertFileWithUrl(filePath, path.basename(new URL(filePath).pathname)) : await uploadDocument(filePath); ``` The URL is then placed into an API request: ```typescript const response = await httpPost('/api/file/process', { fileUrls: [{ fileUrl, fileName, fileType: 'document' }] }, token); ``` ### Technical Analysis Remote inputs are validated only by checking whether the string begins with `http://` or `https://` and whether the URL path has a supported file extension. The implementation does not reject: - Loopback destinations - Private or link-local network addresses - Cloud metadata addresses - Embedded URL credentials - Nonstandard ports - DNS names resolving to reserved addresses - Redirect chains leading to restricted destinations The URL is not fetched locally. Instead, it is transmitted to Nota Sign's `/api/file/process` endpoint, where the downstream service is expected to retrieve it. This creates a potential server-side request forgery condition in Nota Sign infrastructure if that service does not independently enforce destination restrictions. Accepting a user-provided public document URL is part of the declared funct ...[truncated 1549 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require HTTPS unless plain HTTP is explicitly necessary. 2. Reject URLs containing embedded usernames or passwords. 3. Reject loopback, private, link-local, multicast, unspecified, and other reserved IPv4 and IPv6 destinations. 4. Resolve hostnames before submission and validate every returned address. 5. Defend against DNS rebinding by ensuring the downstream fetcher pins and revalidates the resolved destination. 6. Restrict destination ports to an explicit allowlist, normally port 443. 7. Limit redirects and validate every redirect destination using the same policy. 8. Consider allowing only approved document-hosting domains or requiring local upload instead of arbitrary remote retrieval. 9. Ensure Nota Sign's server-side fetcher independently applies equivalent SSRF controls; client-side validation alone must not be treated as sufficient. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (25)

Ae1

High
Category
analysis-evasion
Content
nvironment selection, file validation, signer collection, and execution through scripts/send_envelope.ts, with a temporary node@20 fallback when local Node.js i
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
nvironment selection, file validation, signer collection, and execution through scripts/send_envelope.ts, with a temporary node@20 fallback when local Node.js i
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
nvironment selection, file validation, signer collection, and execution through scripts/send_envelope.ts, with a temporary node@20 fallback when local Node.js i
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
nvironment selection, file validation, signer collection, and execution through scripts/send_envelope.ts, with a temporary node@20 fallback when local Node.js i
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
nvironment selection, file validation, signer collection, and execution through scripts/send_envelope.ts, with a temporary node@20 fallback when local Node.js i
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Credential Access

High
Category
Privilege Escalation
Content
}, '');

  if (!response.success || !response.data) {
    throw new Error('Failed to get access token: ' + (response.message || response.code));
  }

  cachedAccessToken = response.data.accessToken;
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The README explains how to send local files or URLs for signature but does not clearly warn that document contents, file metadata, signer names, and email addresses will be transmitted to an external e-signature provider. In a skill centered on handling potentially sensitive documents, missing disclosure can lead to inadvertent data exfiltration or policy violations by users who assume processing is local.

Rp1

Medium
Category
MCP Rug Pull
Confidence
90% confidence
Finding
The README instructs users to run `npx tsx` without pinning an exact package version. Because `npx` may fetch the latest package from the registry at execution time, a compromised upstream package, typo-squatted dependency, or breaking update could lead to unreviewed code execution on the operator's machine.

Rp1

Medium
Category
MCP Rug Pull
Confidence
90% confidence
Finding
This command again relies on unpinned `npx tsx`, which can download and execute whatever version is current at runtime. In a skill that handles sensitive documents and credentials, this increases supply-chain risk because execution happens before any document handling safeguards matter.

Rp1

Medium
Category
MCP Rug Pull
Confidence
95% confidence
Finding
The fallback command dynamically fetches both `node@20` and `tsx` from npm for immediate execution, and `tsx` is not pinned to a specific version. This creates a broader supply-chain execution surface because multiple packages are being pulled at runtime, including in environments already lacking the preferred local runtime.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
- `PROD` and `UAT` do not share the same credential set
- when switching environments, replace `appId`, `appKey`, `userCode`, and `serverRegion` with values issued for that target environment
- changing only `"environment": "PROD"` to `"environment": "UAT"` is not enough
- protect the file with `chmod 600 ~/.notasign/config.json`

## Parameters
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Rp1

Medium
Category
MCP Rug Pull
Confidence
90% confidence
Finding
This initialization command uses unpinned `npx tsx`, so users may execute an unreviewed version from the registry while entering or processing Nota Sign credentials. That creates a realistic path for credential theft or arbitrary code execution if the package supply chain is compromised.

Rp1

Medium
Category
MCP Rug Pull
Confidence
90% confidence
Finding
The example for sending documents to one signer uses unpinned `npx tsx`, which can result in execution of changing third-party code at invocation time. Because this workflow processes potentially confidential documents and signer metadata, the context makes the supply-chain risk more sensitive than a generic dev tool example.

Rp1

Medium
Category
MCP Rug Pull
Confidence
90% confidence
Finding
This multi-signer send example repeats the same unpinned `npx tsx` pattern. An attacker who compromises the package distribution path could gain code execution in the same session where sensitive document paths, recipient data, and service credentials are present.

Rp1

Medium
Category
MCP Rug Pull
Confidence
95% confidence
Finding
The troubleshooting guidance recommends a fallback using runtime-fetched `node@20` and `tsx`, again with `tsx` unpinned and `node` only loosely versioned. This is especially risky because troubleshooting steps are likely to be copied verbatim by users under time pressure, increasing the chance of executing unreviewed packages from npm.

Lp3

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The skill performs operations that require filesystem, environment, and network access, but it does not declare an explicit tool/permission scope. That makes the agent's effective capabilities ambiguous and can lead to overbroad execution in environments where least-privilege controls depend on the manifest.

Session Persistence

Medium
Category
Rogue Agent
Content
## Workflow

1. Check whether `./notasign-config.json` exists; if not, check `~/.notasign/config.json`.
2. If config is missing, ask only for the missing credential fields and write the config file.
3. If the user wants to switch between `PROD` and `UAT`, do not only flip `environment`. Collect the full target-environment values for `appId`, `appKey`, `userCode`, and `serverRegion`, then rewrite the config.
4. Validate the file input before sending:
   - local path must exist, or
Confidence
91% confidence
Finding
The skill persists sensitive Nota Sign credentials, including a private key, to local config files in `./notasign-config.json` or `~/.notasign/config.json`. Persistent local storage of secrets increases exposure to theft by other local processes, accidental inclusion in repositories, or reuse across sessions beyond the user's immediate intent.

Rp1

Medium
Category
MCP Rug Pull
Confidence
97% confidence
Finding
The skill instructs the agent to run code through `npx tsx` and also to dynamically install `node@20` and `tsx` from npm at execution time without pinning immutable versions or integrity hashes. This creates a supply-chain risk: a compromised or unexpected package version could execute arbitrary code with access to the document, credentials, config files, and network.

Description-Behavior Mismatch

Medium
Confidence
92% confidence
Finding
The manifest says the skill can 'send a signing link,' but this file only uploads/Processes documents and calls /api/envelope/create with autoSend enabled. There is no code path to retrieve, generate, or send a signing link to users, so the implemented behavior is narrower than the declared capability.

Session Persistence

Medium
Category
Rogue Agent
Content
try {
      await fs.mkdir(homeConfigDir, { recursive: true });
    } catch (error) {
      return { success: false, message: `Failed to create config directory: ${error}` };
    }
  }
Confidence
87% confidence
Finding
The script stores long-lived authentication material, including the private signing key, in a persistent config file under the user's home or working directory. In this context, persistent secret storage expands the window for compromise and may expose credentials to other local users, backups, or accidentally committed project files.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The script persistently writes the Base64-encoded PKCS#8 private key to disk in plaintext JSON without warning the user or applying file-permission hardening. In a skill that handles signing credentials, local disclosure of this config would allow misuse of the private key and unauthorized API authentication/signing.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding
npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Natural-Language Policy Violations

Medium
Confidence
98% confidence
Finding
The interactive workflow switches to Chinese-only prompts and status strings such as `交互模式`, `文件路径`, and `信封发送成功` without asking the user for a preferred language. This is a natural-language policy issue because it imposes a locale/language choice rather than offering an option or documenting a justified regional constraint.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The script uploads document contents to external Nota Sign endpoints as part of normal operation, but interactive mode does not provide an explicit disclosure or confirmation at send time. In an agent-skill context, users may provide local file paths expecting local processing, so silent transfer of potentially sensitive documents to third-party services increases data exposure risk.

Description-Behavior Mismatch

Low
Confidence
90% confidence
Finding
The description advertises support for 'uploaded attachment' input, but validateInputFile and the rest of the flow only recognize local file paths and remote URLs. There is no attachment-resolution logic or integration with any attachment abstraction in this file.

Static analysis

Detected: suspicious.env_credential_access, suspicious.exposed_secret_literal

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
scripts/send_envelope.ts:457

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
scripts/send_envelope.ts:110