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.
- package/lib/src/maintenance/detect.js +3 -3
- package/lib/src/rules/helpers/require-story-visitors.js +5 -16
- package/lib/src/rules/require-story-annotation.js +9 -14
- package/lib/tests/maintenance/detect-isolated.test.js +68 -1
- package/lib/tests/maintenance/report.test.js +2 -2
- package/package.json +1 -1
|
@@ -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.
|
|
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
|
-
*
|
|
68
|
-
*
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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.
|
|
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",
|