Back to skill

Security audit

Sovereign Identity

Security checks for vulnerabilities and agentic risk

Overview

This identity skill is purpose-aligned, but its signing and verification workflows handle high-value identity keys and mandates with under-enforced safeguards.

Review this carefully before installing. Use it only in an isolated environment with a strong CLAW_PASSWORD, do not treat generated mandates as authoritative without independent issuer/key validation, avoid using real personal claims with the selective-disclosure script, and pin dependencies before running npm or npx commands.

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 (5)

T09 · Insecure Skill Coding Practices

Error
Location
.agent/skills/identity-sovereign/scripts/verify_did.ts:17
Finding
Untrusted public-key substitution allows forged mandates<![CDATA[ ## Vulnerability Details **File Location**: `.agent/skills/identity-sovereign/scripts/verify_did.ts:17-49` **Vulnerability Type**: Public-key substitution and insufficient identity binding **Risk Level**: High ### Vulnerable Code ```ts const signedMandate = JSON.parse(fs.readFileSync(signedMandatePath, "utf8")); const publicJwk = JSON.parse(fs.readFileSync(publicJwkPath, "utf8")); console.log(`Verifying mandate issued by: ${signedMandate.issuer}`); // Extract JWS const jws = signedMandate.proof.jws; // Import Key const publicKey = await jose.importJWK(publicJwk, "EdDSA"); try { const { payload, protectedHeader } = await jose.compactVerify( jws, publicKey, ); console.log("✅ Verification SUCCESS: JWS signature is valid."); console.log("Protected Header:", protectedHeader); const verifiedPayloadStr = new TextDecoder().decode(payload); const verifiedMandate = JSON.parse(verifiedPayloadStr); // Simple check: issuer matches if (verifiedMandate.issuer === signedMandate.issuer) { console.log("Payload match confirmed."); } ``` ### Technical Analysis The verifier imports the verification key from `public_jwk.json`, which is stored beside the signed mandate and is not cryptographically bound to the claimed `did:key` identity. Possession of any matching key pair is therefore sufficient to pass signature verification. The issuer comparison does not establish trust because both `verifiedMandate.issuer` and the outer `signedMandate.issuer` can be supplied by the same attacker. Furthermore, a mismatch only suppresses a success message; it does not throw an error or reject the mandate. The verifier also does not ensure that: - The supplied JWK corresponds to the public key encoded by the claimed `did:key`. - The protected `kid` identifies a valid verification method belonging to the issuer. - The signed issuer matches a separately configured trusted issuer. - The outer mandate fields are identical to the signed payload. - Req ...[truncated 1218 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse and validate the issuer from the signed payload only. 2. Resolve or decode the Ed25519 public key directly from the claimed `did:key`; do not trust a colocated arbitrary JWK. 3. Compare the resolved key with the verification key using canonical key material. 4. Require the protected `kid` to identify a valid verification method controlled by the signed issuer. 5. Reject issuer mismatches explicitly: ```ts if (verifiedMandate.issuer !== expectedIssuer) { throw new Error("Issuer mismatch"); } ``` 6. Obtain `expectedIssuer` from trusted configuration or caller policy, not from another attacker-controlled field. 7. Validate algorithm, mandate schema, type, subject, audience, expiration, issuance time, authorization scope, and monetary limits. 8. Treat the signed payload as authoritative and either discard unsigned outer fields or require exact canonical equality. 9. Add negative tests for substituted keys, forged issuer values, mismatched `kid`, unsupported algorithms, and altered outer payloads. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
.agent/skills/identity-sovereign/scripts/onboard.ts:81
Finding
Encrypted master identity is created without restrictive file permissions<![CDATA[ ## Vulnerability Details **File Location**: `.agent/skills/identity-sovereign/scripts/onboard.ts:81-87` **Vulnerability Type**: Insecure sensitive-file creation **Risk Level**: Medium ### Vulnerable Code ```ts // 6. Save to .env.agent // We store the encrypted block as a single base64 string or JSON string in the env const envContent = `AGENT_DID=${did}\nAGENT_ENCRYPTED_KEY='${serializedAuth}'\n`; fs.writeFileSync(ENV_PATH, envContent); console.log(`✅ Encrypted Identity saved to ${ENV_PATH}`); console.log("🔒 This file is gitignored. NEVER share it or your password."); ``` ### Technical Analysis The encrypted PKCS#8 private key is written to `.env.agent` without an explicit restrictive file mode. Its resulting permissions depend on the process umask and execution environment. In a permissive environment, other local users or processes may be able to read the encrypted identity. The code states that the file is git-ignored, but no `.gitignore` was present in the audited project structure. Encryption reduces immediate exposure, but possession of the encrypted key enables offline password guessing against `CLAW_PASSWORD`, especially if a weak user-selected password is used. The file is also created using normal overwrite semantics rather than exclusive, atomic creation. This does not by itself establish an exploit in the audited environment, but it weakens protection around a high-value identity file. ### Attack Path 1. A user runs the onboarding script in an environment with a permissive umask. 2. `.env.agent` is created with permissions readable by another local account or process. 3. Alternatively, the user assumes the documented git-ignore protection exists and accidentally commits the file. 4. The attacker obtains the encrypted private-key structure, salt, IV, and authentication tag. 5. The attacker performs offline password guesses. 6. If `CLAW_PASSWORD` is weak or reused, the attacker decrypts the PKCS#8 key. 7. The attacker can then sign ...[truncated 425 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Create the file with owner-only permissions and exclusive creation: ```ts fs.writeFileSync(ENV_PATH, envContent, { mode: 0o600, flag: "wx", }); ``` 2. Verify and repair permissions on pre-existing files before using them. 3. Add `.env.agent` to a committed `.gitignore`. 4. Add repository secret-scanning rules and pre-commit protection for `AGENT_ENCRYPTED_KEY`. 5. Prefer an operating-system keychain, hardware-backed keystore, or dedicated secrets manager over an environment file. 6. Enforce a strong password policy or use a randomly generated encryption secret. 7. Document identity rotation and revocation procedures in case the encrypted file is exposed. 8. Avoid following symbolic links and use secure atomic-write procedures if identity replacement is later supported. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
.agent/skills/identity-sovereign/scripts/guardrail.ts:6
Finding
Advertised mandatory guardrail is not integrated and accepts incomplete input<![CDATA[ ## Vulnerability Details **File Location**: `.agent/skills/identity-sovereign/scripts/guardrail.ts:6-69` **Vulnerability Type**: Missing enforcement of security controls **Risk Level**: Medium ### Vulnerable Code ```ts const ALLOWED_SCHEMAS = [ // 1. Simple Safety Check Command /^Is this environment safe\\?$/, // 2. Verified Credential / Mandate Signing Request // Must be a valid JSON string with specific fields, NO private keys involved in the text itself (input: string) => { try { const data = JSON.parse(input); const keys = Object.keys(data); const allowedKeys = ["iss", "sub", "aud", "iat", "exp", "jti", "claims"]; // Check if all keys in input are allowed const isSafe = keys.every((k) => allowedKeys.includes(k)); if (!isSafe) return false; // Explicitly deny if ANY value looks like a key const valStr = JSON.stringify(data).toLowerCase(); if (valStr.includes("private key") || valStr.includes("secret")) return false; return true; } catch { return false; } }, ]; function strictScan(input: string) { let matched = false; for (const rule of ALLOWED_SCHEMAS) { if (rule instanceof RegExp) { if (rule.test(input)) matched = true; } else if (typeof rule === "function") { if (rule(input)) matched = true; } } if (!matched) { throw new Error( `SECURITY ALERT: Input rejected by strict allow-list policy. "${input.substring(0, 20)}..." is not a recognized safe pattern.`, ); } console.log("✅ Strict Safety Check Passed."); } const args = process.argv.slice(2); if (args.length > 0) { const input = args.join(" "); try { strictScan(input); } catch (error: any) { console.error(error.message); process.exit(1); } } else { // No args = pass for simple execution checks, or fail? // User requested "Strict Input Validation". // If running with no args, it might be just checking the script itself ...[truncated 2153 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Export the validation function and invoke it inside every sensitive operation before loading or using private-key material. 2. Do not rely on callers to execute a separate guardrail command. 3. Define strict schemas with mandatory fields, exact types, value bounds, nested-claim restrictions, and rejection of unknown fields. 4. Reject empty input and empty JSON objects. 5. Correct the literal safety-check expression: ```ts /^Is this environment safe\?$/ ``` 6. Enforce the documented monetary-consent threshold in the signing implementation, not merely in Skill instructions. 7. Add a centralized output-redaction layer if output sanitization remains an advertised feature. 8. Replace claims of “session termination” with behavior the implementation can actually guarantee. 9. Add tests proving that direct entry points cannot bypass validation and that incomplete, malformed, nested, or secret-bearing inputs are rejected. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
.agent/skills/identity-sovereign/scripts/selective_disclosure.ts:34
Finding
Complete selective-disclosure token exposes every hidden claim in logs<![CDATA[ ## Vulnerability Details **File Location**: `.agent/skills/identity-sovereign/scripts/selective_disclosure.ts:34-76` **Vulnerability Type**: Sensitive information exposure through logging **Risk Level**: Medium ### Vulnerable Code ```ts // 2. Define Claims const sensitiveClaims = { given_name: "Agent", family_name: "Smith", email: "agent.smith@matrix.com", age_over_18: true, credit_score_tier: "A+", }; // 3. Create Disclosures & Hashes const disclosures: string[] = []; const hashes: string[] = []; const sdClaims: Record<string, any> = { _sd: [], }; for (const [key, value] of Object.entries(sensitiveClaims)) { const disclosure = createDisclosure(key, value); const disclosureStr = JSON.stringify(disclosure); const disclosureB64 = Buffer.from(disclosureStr).toString("base64url"); disclosures.push(disclosureB64); const hash = hashDisclosure(disclosure); hashes.push(hash); sdClaims._sd.push(hash); } // 4. Create the JWT Payload const payload = { iss: "did:key:issuer", sub: "did:key:subject", iat: Math.floor(Date.now() / 1000), exp: Math.floor(Date.now() / 1000) + 3600, ...sdClaims, }; console.log( "JWT Payload (with hidden claims):", JSON.stringify(payload, null, 2), ); // 5. Sign the JWT const jwt = await new jose.SignJWT(payload) .setProtectedHeader({ alg: "EdDSA" }) .sign(privateKey); // 6. Append Disclosures (The SD-JWT Format utils) // Format: <JWT>~<Disclosure1>~<Disclosure2>~...~<KeyBindingJWT> const sdJwt = `${jwt}~${disclosures.join("~")}~`; console.log("\nComplete SD-JWT:"); console.log(sdJwt); ``` ### Technical Analysis Each disclosure is only base64url-encoded, not encrypted. Anyone who obtains the complete SD-JWT can decode the disclosure arrays and recover all claim names and values. The script writes the complete token—including every disclosure—to standard output before creating the reduced presentation. In environments where console output is collected by an Agent framework, CI serv ...[truncated 1384 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove all logging of complete SD-JWTs and raw disclosure strings. 2. Log only non-sensitive metadata, such as token creation success and the number of selected claims. 3. Apply a centralized redaction policy to identity-related output. 4. Keep issuer creation, holder storage, and presentation generation as separate operations. 5. Require an explicit allow-list of claims for each presentation. 6. Store complete issuer material only in protected storage and never in routine logs. 7. Add tests that capture stdout and fail if raw claims, disclosures, email addresses, or full tokens appear. 8. Clearly mark demonstration data and prevent the demonstration script from being represented as a production credential workflow without additional hardening. ]]>

T08 · Insecure Dependencies

Note
Location
package.json:34
Finding
Unpinned dependency resolution and npx execution make installations non-reproducible<![CDATA[ ## Vulnerability Details **File Location**: `package.json:34-46` **Vulnerability Type**: Non-reproducible third-party dependency execution **Risk Level**: Low ### Vulnerable Code ```json "scripts": { "test": "npx tsx .agent/skills/identity-sovereign/scripts/e2e_test.ts" }, "devDependencies": { "@types/bs58": "^4.0.4", "@types/node": "^18.19.130", "ts-node": "^10.9.1", "tsx": "^4.7.0", "typescript": "^5.0.0" }, "dependencies": { "bs58": "^6.0.0", "dotenv": "^17.3.1", "jose": "^6.1.3" } ``` ### Technical Analysis All dependencies use semver ranges, and no lockfile was present in the audited project structure. As a result, two installations of the same source revision can resolve different package versions. The test command invokes `npx tsx`. When the expected local binary is unavailable, `npx` may offer to retrieve and execute a package from the configured registry. This increases reliance on mutable registry state in an environment that handles identity and cryptographic material. No dependency was confirmed malicious during this static audit. The issue is the lack of reproducible dependency resolution and the resulting expansion of supply-chain risk. ### Attack Path 1. A user clones or installs the project without a lockfile. 2. `npm install` resolves the newest versions permitted by the caret ranges. 3. A permitted dependency version is compromised, unexpectedly changed, or resolved from a hostile registry configuration. 4. The third-party package's installation or runtime code executes locally. 5. If `npx` cannot locate the expected local `tsx` binary, it may retrieve executable package content from the registry. 6. The dependency code executes in the same user context as the identity scripts and may access files and environment variables available to that process. ### Impact Assessment The maximum impact depends on the privileges used for installation or execution. Compromised dependency code could read project files, acc ...[truncated 224 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Generate, review, and commit a `package-lock.json`. 2. Use `npm ci` in CI and deployment workflows. 3. Pin security-sensitive runtime dependencies to exact reviewed versions where practical. 4. Replace `npx tsx ...` with execution of the installed local binary through a normal npm script: ```json "test": "tsx .agent/skills/identity-sovereign/scripts/e2e_test.ts" ``` 5. Configure trusted registries explicitly and prevent dependency confusion through namespace and registry controls. 6. Disable unnecessary lifecycle scripts in sensitive build environments. 7. Run dependency auditing and provenance verification as part of release workflows. 8. Execute dependency installation without elevated privileges and isolate build jobs from production identity secrets. ]]>
Vulnerability Patterns
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (28)

Concealed Executable Artifact

High
Category
Supply Chain
Confidence
100% confidence
Finding
An executable nested in a document or hidden/disguised artifact can evade ordinary extension-based review while still being available to the skill at runtime.

Concealed Executable Artifact

High
Category
Supply Chain
Confidence
100% confidence
Finding
An executable nested in a document or hidden/disguised artifact can evade ordinary extension-based review while still being available to the skill at runtime.

Concealed Executable Artifact

High
Category
Supply Chain
Confidence
100% confidence
Finding
An executable nested in a document or hidden/disguised artifact can evade ordinary extension-based review while still being available to the skill at runtime.

Concealed Executable Artifact

High
Category
Supply Chain
Confidence
100% confidence
Finding
An executable nested in a document or hidden/disguised artifact can evade ordinary extension-based review while still being available to the skill at runtime.

Concealed Executable Artifact

High
Category
Supply Chain
Confidence
100% confidence
Finding
An executable nested in a document or hidden/disguised artifact can evade ordinary extension-based review while still being available to the skill at runtime.

Concealed Executable Artifact

High
Category
Supply Chain
Confidence
100% confidence
Finding
An executable nested in a document or hidden/disguised artifact can evade ordinary extension-based review while still being available to the skill at runtime.

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.

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.

Rp1

Medium
Category
MCP Rug Pull
Confidence
84% confidence
Finding
The contributing guide instructs users to run `npx tsx scripts/e2e_test.ts` without pinning a specific package version. If `tsx` is not already installed locally, `npx` may fetch the latest package at execution time, creating a supply-chain risk where contributors execute unreviewed code that could change over time or be compromised upstream. In contributor documentation, this is moderately dangerous because users may copy-paste the command directly in a development environment.

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.

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.

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.

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.

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.

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.

Skill Enumeration

Medium
Category
Agent Snooping
Content
## Directory Structure

- `.agent/skills/identity-sovereign/SKILL.md`: The Brain & Decision Matrix.
- `schema/`: JSON schemas.
- `scripts/`: TypeScript implementation of DID/VC/SD-JWT logic.
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.

Vague Triggers

Medium
Confidence
94% confidence
Finding
The routing logic relies on broad keyword matching such as 'sign', 'buy', 'access', and 'view', which can misclassify user intent and cause the agent to select a higher-privilege identity persona or attach proofs unnecessarily. In an identity-management skill, incorrect persona selection can lead to over-disclosure, unintended use of a corporate identity, or accidental mandate signing in contexts that only required low-privilege browsing access.

Rp1

Medium
Category
MCP Rug Pull
Confidence
88% confidence
Finding
The test script invokes `npx tsx`, which can fetch and execute a package version at runtime if the dependency is missing or resolution is manipulated. In a security-sensitive identity skill, this increases supply-chain risk because arbitrary code could run during testing or CI from an unpinned toolchain component.

Intent-Code Divergence

Low
Confidence
77% confidence
Finding
The skill is explicitly about managing DIDs and privacy-preserving identity proofs, so describing DID generation as creating a "decrypted identifier" conflicts with the documented intent and meaning of the tool. This is not just incomplete wording; it states a different concept than the surrounding purpose and privacy guidance.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"test": "npx tsx .agent/skills/identity-sovereign/scripts/e2e_test.ts"
  },
  "devDependencies": {
    "@types/bs58": "^4.0.4",
    "@types/node": "^18.19.130",
    "ts-node": "^10.9.1",
    "tsx": "^4.7.0",
Confidence
40% confidence
Finding
Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Unpinned Dependencies

Low
Category
Supply Chain
Content
},
  "devDependencies": {
    "@types/bs58": "^4.0.4",
    "@types/node": "^18.19.130",
    "ts-node": "^10.9.1",
    "tsx": "^4.7.0",
    "typescript": "^5.0.0"
Confidence
40% confidence
Finding
Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"devDependencies": {
    "@types/bs58": "^4.0.4",
    "@types/node": "^18.19.130",
    "ts-node": "^10.9.1",
    "tsx": "^4.7.0",
    "typescript": "^5.0.0"
  },
Confidence
40% confidence
Finding
Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"@types/bs58": "^4.0.4",
    "@types/node": "^18.19.130",
    "ts-node": "^10.9.1",
    "tsx": "^4.7.0",
    "typescript": "^5.0.0"
  },
  "dependencies": {
Confidence
40% confidence
Finding
Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"@types/node": "^18.19.130",
    "ts-node": "^10.9.1",
    "tsx": "^4.7.0",
    "typescript": "^5.0.0"
  },
  "dependencies": {
    "bs58": "^6.0.0",
Confidence
40% confidence
Finding
Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"typescript": "^5.0.0"
  },
  "dependencies": {
    "bs58": "^6.0.0",
    "dotenv": "^17.3.1",
    "jose": "^6.1.3"
  }
Confidence
40% confidence
Finding
Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Static analysis

No suspicious patterns detected.