@heylemon/lemonade 0.2.5 → 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -54,7 +54,12 @@ function buildUserIdentitySection(ownerLine, isMinimal) {
54
54
  function buildTimeSection(params) {
55
55
  if (!params.userTimezone)
56
56
  return [];
57
- return ["## Current Date & Time", `Time zone: ${params.userTimezone}`, ""];
57
+ return [
58
+ "## Current Date & Time",
59
+ `Time zone: ${params.userTimezone}`,
60
+ `When the user mentions a time (e.g. "8pm", "tomorrow at 3") always interpret it in their timezone (${params.userTimezone}) and pass the correct absolute time to any tool or API (calendar events, reminders, alarms, etc.).`,
61
+ "",
62
+ ];
58
63
  }
59
64
  function buildReplyTagsSection(isMinimal) {
60
65
  if (isMinimal)
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.2.5",
3
- "commit": "80887609891b389ba300be9cfe0a533a54f2d4a9",
4
- "builtAt": "2026-02-22T15:54:27.492Z"
2
+ "version": "0.2.7",
3
+ "commit": "927681b91d1de51c0d44cf1cbb1e411e1cd65da4",
4
+ "builtAt": "2026-02-23T03:12:34.259Z"
5
5
  }
@@ -1 +1 @@
1
- f7aff81ae06daba57524cebf957881ed76d554113073508696e255d618aa3b03
1
+ 757ea6899eecd5ce6abd151d44edfd02de6269d0bbf0b14ed5bdb1f2074ae1c6
@@ -49,7 +49,7 @@ function buildAgentPrompt(messagesUnknown) {
49
49
  if (!role || !content)
50
50
  continue;
51
51
  if (role === "system" || role === "developer") {
52
- systemParts.push(content);
52
+ systemParts.push(`<client_instruction>\n${content}\n</client_instruction>`);
53
53
  continue;
54
54
  }
55
55
  const normalizedRole = role === "function" ? "tool" : role;
@@ -109,7 +109,7 @@ export function buildAgentPrompt(input) {
109
109
  if (!content)
110
110
  continue;
111
111
  if (item.role === "system" || item.role === "developer") {
112
- systemParts.push(content);
112
+ systemParts.push(`<client_instruction>\n${content}\n</client_instruction>`);
113
113
  continue;
114
114
  }
115
115
  const normalizedRole = item.role === "assistant" ? "assistant" : "user";
@@ -288,11 +288,12 @@ export async function handleOpenResponsesHttpRequest(req, res, opts) {
288
288
  },
289
289
  limits: limits.files,
290
290
  });
291
+ const safeName = file.filename.replace(/[<>"&\n\r]/g, "_");
291
292
  if (file.text?.trim()) {
292
- fileContexts.push(`<file name="${file.filename}">\n${file.text}\n</file>`);
293
+ fileContexts.push(`<file name="${safeName}">\n${file.text}\n</file>`);
293
294
  }
294
295
  else if (file.images && file.images.length > 0) {
295
- fileContexts.push(`<file name="${file.filename}">[PDF content rendered to images]</file>`);
296
+ fileContexts.push(`<file name="${safeName}">[PDF content rendered to images]</file>`);
296
297
  }
297
298
  if (file.images && file.images.length > 0) {
298
299
  images = images.concat(file.images);
@@ -332,9 +333,13 @@ export async function handleOpenResponsesHttpRequest(req, res, opts) {
332
333
  const prompt = buildAgentPrompt(payload.input);
333
334
  const fileContext = fileContexts.length > 0 ? fileContexts.join("\n\n") : undefined;
334
335
  const toolChoiceContext = toolChoicePrompt?.trim();
335
- // Handle instructions + file context as extra system prompt
336
+ // Handle instructions + file context as extra system prompt.
337
+ // Client-supplied instructions are wrapped in tags to prevent prompt injection.
338
+ const wrappedInstructions = payload.instructions
339
+ ? `<client_instruction>\n${payload.instructions}\n</client_instruction>`
340
+ : undefined;
336
341
  const extraSystemPrompt = [
337
- payload.instructions,
342
+ wrappedInstructions,
338
343
  prompt.extraSystemPrompt,
339
344
  toolChoiceContext,
340
345
  fileContext,
@@ -57,7 +57,19 @@ export async function handleSkillsHttpRequest(req, res, opts) {
57
57
  .replace(/^\/api\/skills\/?/, "")
58
58
  .split("/")
59
59
  .filter(Boolean);
60
- const skillName = pathParts[0] ? decodeURIComponent(pathParts[0]) : undefined;
60
+ const rawSkillName = pathParts[0] ? decodeURIComponent(pathParts[0]) : undefined;
61
+ // Validate skill name to prevent path traversal
62
+ const SAFE_SKILL_NAME_RE = /^[a-z0-9][a-z0-9_-]*$/;
63
+ const skillName = rawSkillName && SAFE_SKILL_NAME_RE.test(rawSkillName) ? rawSkillName : undefined;
64
+ if (rawSkillName && !skillName) {
65
+ sendJson(res, 400, {
66
+ error: {
67
+ message: "Invalid skill name. Use only lowercase letters, numbers, hyphens, and underscores.",
68
+ type: "invalid_request_error",
69
+ },
70
+ });
71
+ return true;
72
+ }
61
73
  try {
62
74
  if (!skillName) {
63
75
  // /api/skills
@@ -85,8 +97,9 @@ export async function handleSkillsHttpRequest(req, res, opts) {
85
97
  }
86
98
  }
87
99
  catch (err) {
100
+ console.error("[skills-http] Internal error:", err);
88
101
  sendJson(res, 500, {
89
- error: { message: String(err), type: "internal_error" },
102
+ error: { message: "An internal error occurred", type: "internal_error" },
90
103
  });
91
104
  return true;
92
105
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heylemon/lemonade",
3
- "version": "0.2.5",
3
+ "version": "0.2.7",
4
4
  "description": "AI gateway CLI for Lemon - local AI assistant with integrations",
5
5
  "publishConfig": {
6
6
  "access": "restricted"