eslint-plugin-traceability 1.6.4 → 1.6.5

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.
@@ -87,12 +87,12 @@ function processFileForStaleAnnotations(file, workspaceRoot, cwd, stale) {
87
87
  }
88
88
  /**
89
89
  * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
90
- * @req REQ-MAINT-DETECT - Handle individual @story matches within a file
90
+ * @req REQ-MAINT-DETECT REQ-SECURITY-VALIDATION - Handle individual @story matches within a file
91
91
  */
92
92
  function handleStoryMatch(storyPath, workspaceRoot, cwd, stale) {
93
93
  // @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
94
- // @req REQ-MAINT-DETECT - Skip traversal/absolute-unsafe story paths before any filesystem or boundary checks
95
- if ((0, storyReferenceUtils_1.isTraversalUnsafe)(storyPath)) {
94
+ // @req REQ-MAINT-DETECT REQ-SECURITY-VALIDATION - Skip traversal/absolute-unsafe or invalid-extension story paths before any filesystem or boundary checks
95
+ if ((0, storyReferenceUtils_1.isUnsafeStoryPath)(storyPath)) {
96
96
  return;
97
97
  }
98
98
  // @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
@@ -13,28 +13,17 @@ const require_story_helpers_1 = require("./require-story-helpers");
13
13
  * @req REQ-BUILD-VISITORS-FNDECL - Provide visitor for FunctionDeclaration
14
14
  */
15
15
  function buildFunctionDeclarationVisitor(context, sourceCode, options) {
16
- /**
17
- * Debug flag for optional visitor logging.
18
- * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
19
- * @req REQ-DEBUG-LOG-TOGGLE - Allow opt-in debug logging via TRACEABILITY_DEBUG
20
- */
21
- const debugEnabled = process.env.TRACEABILITY_DEBUG === "1";
22
16
  /**
23
17
  * Handle FunctionDeclaration nodes.
18
+ *
19
+ * Developers who need to troubleshoot this handler may temporarily add
20
+ * console.debug statements here, but by default no debug logging runs so that
21
+ * file paths and other details are not leaked during normal linting.
22
+ *
24
23
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
25
24
  * @req REQ-ANNOTATION-REQUIRED - Report missing @story on function declarations
26
25
  */
27
26
  function handleFunctionDeclaration(node) {
28
- /**
29
- * Debug logging for visitor entry
30
- * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
31
- * @req REQ-DEBUG-LOG - Provide debug logging for visitor entry
32
- */
33
- if (debugEnabled) {
34
- console.debug("require-story-annotation:FunctionDeclaration", typeof context.getFilename === "function"
35
- ? context.getFilename()
36
- : "<unknown>", node && node.id ? node.id.name : "<anonymous>");
37
- }
38
27
  if (!options.shouldProcessNode(node))
39
28
  return;
40
29
  const target = (0, require_story_helpers_1.resolveTargetNode)(sourceCode, node);
@@ -64,24 +64,19 @@ const rule = {
64
64
  const scope = opts.scope || require_story_helpers_1.DEFAULT_SCOPE;
65
65
  const exportPriority = opts.exportPriority || "all";
66
66
  /**
67
- * Environment-gated debug logging to avoid leaking file paths unless
68
- * explicitly enabled.
67
+ * Optional debug logging for troubleshooting this rule.
68
+ * Developers can temporarily uncomment the block below to log when the rule
69
+ * is activated for a given file during ESLint runs.
69
70
  *
70
71
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
71
72
  * @req REQ-DEBUG-LOG
72
73
  */
73
- const debugEnabled = process.env.TRACEABILITY_DEBUG === "1";
74
- /**
75
- * Debug log at the start of create to help diagnose rule activation in tests.
76
- *
77
- * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
78
- * @req REQ-DEBUG-LOG
79
- */
80
- if (debugEnabled) {
81
- console.debug("require-story-annotation:create", typeof context.getFilename === "function"
82
- ? context.getFilename()
83
- : "<unknown>");
84
- }
74
+ // console.debug(
75
+ // "require-story-annotation:create",
76
+ // typeof context.getFilename === "function"
77
+ // ? context.getFilename()
78
+ // : "<unknown>",
79
+ // );
85
80
  // Local closure that binds configured scope and export priority to the helper.
86
81
  const should = (node) => (0, require_story_helpers_1.shouldProcessNode)(node, scope, exportPriority);
87
82
  // Delegate visitor construction to helper to keep this file concise.
@@ -38,10 +38,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
38
38
  * @story docs/stories/009.0-DEV-MAINTENANCE-TOOLS.story.md
39
39
  * @req REQ-MAINT-DETECT - Detect stale annotation references
40
40
  */
41
- const fs = __importStar(require("fs"));
42
41
  const path = __importStar(require("path"));
43
42
  const os = __importStar(require("os"));
44
43
  const detect_1 = require("../../src/maintenance/detect");
44
+ const fs = require("fs");
45
45
  describe("detectStaleAnnotations isolated (Story 009.0-DEV-MAINTENANCE-TOOLS)", () => {
46
46
  it("[REQ-MAINT-DETECT] returns empty array when directory does not exist", () => {
47
47
  const result = (0, detect_1.detectStaleAnnotations)("non-existent-dir");
@@ -107,4 +107,71 @@ describe("detectStaleAnnotations isolated (Story 009.0-DEV-MAINTENANCE-TOOLS)",
107
107
  }
108
108
  }
109
109
  });
110
+ /**
111
+ * [REQ-MAINT-DETECT]
112
+ * Ensure detectStaleAnnotations performs security validation for unsafe
113
+ * and invalid-extension story paths and does not perform filesystem checks
114
+ * for malicious @story paths that escape the workspace
115
+ * (Story 009.0-DEV-MAINTENANCE-TOOLS).
116
+ */
117
+ it("[REQ-MAINT-DETECT] performs security validation for unsafe and invalid-extension story paths without stat'ing outside workspace", () => {
118
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "tmp-workspace-"));
119
+ const maliciousRelative = "../outside-project.story.md";
120
+ const maliciousAbsolute = "/etc/passwd.story.md";
121
+ const traversalInside = "nested/../inside.story.md";
122
+ const invalidExtension = "invalid.txt";
123
+ const filePath = path.join(tmpDir, "file.ts");
124
+ const content = `
125
+ /**
126
+ * @story ${maliciousRelative}
127
+ * @story ${maliciousAbsolute}
128
+ * @story ${traversalInside}
129
+ * @story ${invalidExtension}
130
+ * @story legitimate.story.md
131
+ */
132
+ `;
133
+ fs.writeFileSync(filePath, content, "utf8");
134
+ const existsCalls = [];
135
+ const originalExistsSync = fs.existsSync;
136
+ const existsSpy = jest
137
+ .spyOn(fs, "existsSync")
138
+ .mockImplementation((p) => {
139
+ const strPath = typeof p === "string" ? p : p.toString();
140
+ existsCalls.push(strPath);
141
+ return originalExistsSync(p);
142
+ });
143
+ try {
144
+ (0, detect_1.detectStaleAnnotations)(tmpDir);
145
+ const allPathsChecked = [...existsCalls];
146
+ // Ensure no raw malicious values were checked
147
+ expect(allPathsChecked).not.toContain(maliciousRelative);
148
+ expect(allPathsChecked).not.toContain(maliciousAbsolute);
149
+ expect(allPathsChecked).not.toContain(invalidExtension);
150
+ // Also ensure no resolved variants of these paths were checked
151
+ const resolvedRelative = path.resolve(tmpDir, maliciousRelative);
152
+ const resolvedAbsolute = path.resolve(maliciousAbsolute);
153
+ const resolvedInvalid = path.resolve(tmpDir, invalidExtension);
154
+ expect(allPathsChecked).not.toContain(resolvedRelative);
155
+ expect(allPathsChecked).not.toContain(resolvedAbsolute);
156
+ expect(allPathsChecked).not.toContain(resolvedInvalid);
157
+ expect(allPathsChecked.some((p) => p.includes("outside-project.story.md"))).toBe(false);
158
+ expect(allPathsChecked.some((p) => p.includes("passwd.story.md"))).toBe(false);
159
+ expect(allPathsChecked.some((p) => p.includes("invalid.txt"))).toBe(false);
160
+ // traversalInside normalizes within workspace and should be checked
161
+ const resolvedTraversalInside = path.resolve(tmpDir, traversalInside);
162
+ expect(allPathsChecked).toContain(resolvedTraversalInside);
163
+ // legitimate in-workspace .story.md path should also be checked
164
+ const resolvedLegit = path.resolve(tmpDir, "legitimate.story.md");
165
+ expect(allPathsChecked).toContain(resolvedLegit);
166
+ }
167
+ finally {
168
+ existsSpy.mockRestore();
169
+ try {
170
+ fs.rmSync(tmpDir, { recursive: true, force: true });
171
+ }
172
+ catch {
173
+ // ignore cleanup errors
174
+ }
175
+ }
176
+ });
110
177
  });
@@ -58,10 +58,10 @@ describe("generateMaintenanceReport (Story 009.0-DEV-MAINTENANCE-TOOLS)", () =>
58
58
  it("[REQ-MAINT-REPORT] should report stale story annotation", () => {
59
59
  const filePath = path.join(tmpDir, "stub.md");
60
60
  const content = `/**
61
- * @story non-existent.md
61
+ * @story non-existent.story.md
62
62
  */`;
63
63
  fs.writeFileSync(filePath, content);
64
64
  const report = (0, report_1.generateMaintenanceReport)(tmpDir);
65
- expect(report).toContain("non-existent.md");
65
+ expect(report).toContain("non-existent.story.md");
66
66
  });
67
67
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-traceability",
3
- "version": "1.6.4",
3
+ "version": "1.6.5",
4
4
  "description": "A customizable ESLint plugin that enforces traceability annotations in your code, ensuring each implementation is linked to its requirement or test case.",
5
5
  "main": "lib/src/index.js",
6
6
  "types": "lib/src/index.d.ts",