T09 · Insecure Skill Coding Practices
- Location
src/core/memory.js:9- Finding
Sensitive Career and Identity Data Is Stored Without Explicit Restrictive Permissions
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
LinkedCareer is a coherent local career-profile and resume tool, with privacy and supply-chain hardening issues users should understand before installing.
Before installing, be comfortable storing resume, contact, education, and job-history data locally in plaintext under the LinkedCareer user-data directory, with a duplicate Markdown copy. Use it on a private device, protect or delete those files when needed, and be aware that installation downloads npm dependencies without a committed lockfile.
src/core/memory.js:9Sensitive Career and Identity Data Is Stored Without Explicit Restrictive Permissions
templates/resume_professional.html:99Unescaped User-Controlled Achievement Content in the HTML Resume Template
SKILL.md:11Production Dependency Installation Is Not Locked to a Reproducible Dependency Graph
The declared description presents a narrow career/resume function, but the implementation reportedly performs additional actions such as writing files, cover-letter generation, JD matching, and Word export without corresponding disclosure or permissions. This mismatch is dangerous because users and reviewers may approve the skill under false assumptions, while the runtime behavior can create or overwrite local files and process more data than expected.
The skill declares runtime execution and detected access to environment capabilities but does not define any explicit tool scope or permissions. In an agent ecosystem, missing scope declarations weaken reviewability and can allow the skill to access sensitive environment data beyond what users would reasonably expect from a resume tool.
The design explicitly collects and stores highly sensitive personal and career data, including contact details, education, employment history, and uploaded resumes, but does not mention local encryption, access controls, retention limits, or user-facing warnings about device compromise and file exposure. Although local-only storage reduces third-party exfiltration risk, plaintext storage under a predictable path can still expose users if the host is shared, malware-infected, backed up insecurely, or synced automatically.
The file title and the entire skill design are presented in Chinese, and there is no natural-language indication that users may choose another language or locale. Under the policy criteria, forcing a specific language without opt-in can be a locale-policy violation unless it is explicitly justified as region-specific.
The skill explicitly solicits sensitive personal data including name, phone number, email, city, age, gender, and even an old resume, but provides no notice about storage, retention, sharing, or safe handling. In a career-management context this increases privacy risk because users are encouraged to submit a large amount of personally identifiable and potentially employment-sensitive information without informed consent or minimization.
All user-facing prompts and examples in the skill are written in Chinese, and the extraction logic is also tailored to Chinese-language patterns, with no indication that the user can choose another language. This can violate language/locale policy when a skill imposes a specific language without opt-in or documented regional scope.
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.
// 是否正在进行深度梳理
this.isDeepDiving = false
// 深度梳理的经历列表(优先最近5年)
this.deepDiveExpList = []
// 是否已经完成最近5年的梳理,待确认是否继续更早的经历
this.needConfirmMoreDive = false
}
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.
// 是否正在进行深度梳理
this.isDeepDiving = false
// 深度梳理的经历列表(优先最近5年)
this.deepDiveExpList = []
// 是否已经完成最近5年的梳理,待确认是否继续更早的经历
this.needConfirmMoreDive = false
}
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.
// 是否正在进行深度梳理
this.isDeepDiving = false
// 深度梳理的经历列表(优先最近5年)
this.deepDiveExpList = []
// 是否已经完成最近5年的梳理,待确认是否继续更早的经历
this.needConfirmMoreDive = false
}
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.
// 是否正在进行深度梳理
this.isDeepDiving = false
// 深度梳理的经历列表(优先最近5年)
this.deepDiveExpList = []
// 是否已经完成最近5年的梳理,待确认是否继续更早的经历
this.needConfirmMoreDive = false
}
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.
// 是否正在进行深度梳理
this.isDeepDiving = false
// 深度梳理的经历列表(优先最近5年)
this.deepDiveExpList = []
// 是否已经完成最近5年的梳理,待确认是否继续更早的经历
this.needConfirmMoreDive = false
}
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.
// 是否正在进行深度梳理
this.isDeepDiving = false
// 深度梳理的经历列表(优先最近5年)
this.deepDiveExpList = []
// 是否已经完成最近5年的梳理,待确认是否继续更早的经历
this.needConfirmMoreDive = false
}
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.
// 是否正在进行深度梳理
this.isDeepDiving = false
// 深度梳理的经历列表(优先最近5年)
this.deepDiveExpList = []
// 是否已经完成最近5年的梳理,待确认是否继续更早的经历
this.needConfirmMoreDive = false
}
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.
// 是否正在进行深度梳理
this.isDeepDiving = false
// 深度梳理的经历列表(优先最近5年)
this.deepDiveExpList = []
// 是否已经完成最近5年的梳理,待确认是否继续更早的经历
this.needConfirmMoreDive = false
}
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.
// 是否正在进行深度梳理
this.isDeepDiving = false
// 深度梳理的经历列表(优先最近5年)
this.deepDiveExpList = []
// 是否已经完成最近5年的梳理,待确认是否继续更早的经历
this.needConfirmMoreDive = false
}
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.
// 是否正在进行深度梳理
this.isDeepDiving = false
// 深度梳理的经历列表(优先最近5年)
this.deepDiveExpList = []
// 是否已经完成最近5年的梳理,待确认是否继续更早的经历
this.needConfirmMoreDive = false
}
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.
// 是否正在进行深度梳理
this.isDeepDiving = false
// 深度梳理的经历列表(优先最近5年)
this.deepDiveExpList = []
// 是否已经完成最近5年的梳理,待确认是否继续更早的经历
this.needConfirmMoreDive = false
}
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.
// 是否正在进行深度梳理
this.isDeepDiving = false
// 深度梳理的经历列表(优先最近5年)
this.deepDiveExpList = []
// 是否已经完成最近5年的梳理,待确认是否继续更早的经历
this.needConfirmMoreDive = false
}
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.
// 是否正在进行深度梳理
this.isDeepDiving = false
// 深度梳理的经历列表(优先最近5年)
this.deepDiveExpList = []
// 是否已经完成最近5年的梳理,待确认是否继续更早的经历
this.needConfirmMoreDive = false
}
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.
// 是否正在进行深度梳理
this.isDeepDiving = false
// 深度梳理的经历列表(优先最近5年)
this.deepDiveExpList = []
// 是否已经完成最近5年的梳理,待确认是否继续更早的经历
this.needConfirmMoreDive = false
}
The comment at L119 states that older experiences will be loaded for continued deep-dive questioning, but the code again filters on exp.endTime, which is not the field populated for work dates elsewhere in the file. This contradicts the documented behavior because the branch intended to select earlier experiences is not operating on the stored interview data shape.
The inline comment at L252 says the code will prioritize experiences from the last 5 years, but the actual filter reads exp.endTime even though experience dates are stored earlier under the field name time (L234 and similar). Because endTime is never populated in this file, the filter does not implement the documented intent and may incorrectly exclude recent entries before falling back to all experiences.
The file contains user-facing comments, console output, and formatted results entirely in Chinese, including progress logs and final recommendation text. Under the policy, forcing a specific language without opt-in or a documented regional justification is a natural-language policy violation.
The save flow persists structured career data, which can include personal identifiers like name, phone number, email, education, and work history, to files under the user's home directory. This code performs the writes silently and does not include a confirmation prompt, logging, or inline disclosure about the storage behavior.
This method converts potentially sensitive career information into a markdown record and writes it to career_records.md. The operation affects user data on disk, but the code provides no confirmation, visible notice, or explanatory comment indicating that a second human-readable copy will be created.
No suspicious patterns detected.