T09 · Insecure Skill Coding Practices
- Location
SKILL.md:78- Finding
Credential fields may cross the persistence boundary and be exposed in API responses
- Content
View full analysis
; ``` ```typescript export class UserService { constructor(private db: Database) {} async create(input: CreateUserInput): Promise { const existing = await this.db.findUserByEmail(input.email); if (existing) throw new ConflictError('Email already registered'); const passwordHash = await hash(input.password); return this.db.createUser({ ...input, passwordHash }); } } ``` ```typescript app.post('/api/users', async (c) => { const body = createUserSchema.parse(await c.req.json()); const user = await userService.create(body); return c.json(user, 201); }); ``` ```typescript app.get('/api/me', authenticate, async (c) => { const user = await userService.getById(c.get('userId')); return c.json(user); }); ``` ### Technical Analysis The validated `CreateUserInput` contains the user's plaintext `password`. The service hashes that password, but then uses `{ ...input, passwordHash }` when calling the database layer. Consequently, both `password` and `passwordHash` cross the persistence boundary. Whether the plaintext password is ultimately stored depends on the database implementation, but the pattern unnecessarily exposes it to ORM hooks, query logging, instrumentation, permissive persistence methods, and future schema changes. An implementation that accepts all supplied fields may store the plaintext password alongside its hash. The create-user and authenticated profile routes also serialize the returned `User` object directly. The documented user schema includes `pass ...[truncated 1704 chars]- Remediation
View remediation
