@ryuenn3123/agentic-senior-core 5.8.26 → 5.9.0
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/.agents/plugins/agentic-senior-core/hooks/lib/known-security-patterns.json +26 -0
- package/.agents/plugins/agentic-senior-core/hooks/post-edit-enforce.js +65 -1
- package/.agents/plugins/agentic-senior-core/plugin.json +1 -1
- package/.agents/plugins/agentic-senior-core/skills/asc-add-feature/SKILL.md +6 -5
- package/.agents/plugins/agentic-senior-core/skills/asc-new-project/SKILL.md +1 -0
- package/gemini-extension.json +1 -1
- package/package.json +1 -1
- package/plugin.yaml +1 -1
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "Regex patterns for recurring security anti-patterns",
|
|
3
|
+
"patterns": [
|
|
4
|
+
{
|
|
5
|
+
"id": "insecure-redirect",
|
|
6
|
+
"regex": "location\\.href\\s*=\\s*(?!['\"`])([^;\\n]+)",
|
|
7
|
+
"message": "Unvalidated redirect target assigned to location.href. Ensure the variable is sanitized or use a safe routing method."
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"id": "timing-unsafe-compare",
|
|
11
|
+
"regex": "(password|secret|token|key)\\s*(===|!==|==|!=)",
|
|
12
|
+
"message": "Non-timing-safe string comparison on a secret variable. Use crypto.timingSafeEqual instead."
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"id": "user-input-http",
|
|
16
|
+
"regex": "(axios|fetch|got|superagent)\\s*\\(\\s*.*?(req\\.(query|body|params)|process\\.env)",
|
|
17
|
+
"message": "Potentially unsafe input passed directly into an HTTP client. Validate and sanitize URL parameters first."
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"fileSpecific": {
|
|
21
|
+
"Dockerfile": {
|
|
22
|
+
"require": "^(?=.*\\nUSER\\s).*$",
|
|
23
|
+
"message": "Missing USER instruction in Dockerfile. The container will run as root by default."
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -29,6 +29,14 @@ try {
|
|
|
29
29
|
}
|
|
30
30
|
} catch (_) {}
|
|
31
31
|
|
|
32
|
+
let SECURITY_PATTERNS = { patterns: [], fileSpecific: {} };
|
|
33
|
+
try {
|
|
34
|
+
const secPath = path.join(__dirname, 'lib', 'known-security-patterns.json');
|
|
35
|
+
if (fs.existsSync(secPath)) {
|
|
36
|
+
SECURITY_PATTERNS = JSON.parse(fs.readFileSync(secPath, 'utf8'));
|
|
37
|
+
}
|
|
38
|
+
} catch (_) {}
|
|
39
|
+
|
|
32
40
|
const SOURCE_EXTENSIONS = new Set([
|
|
33
41
|
'js', 'ts', 'mjs', 'cjs', 'jsx', 'tsx',
|
|
34
42
|
'py', 'rb', 'go', 'rs', 'java', 'kt', 'swift', 'cs',
|
|
@@ -145,7 +153,6 @@ function processSingleEdit(toolName, toolInput, emitFn, skipArray) {
|
|
|
145
153
|
checkDependencyAddition(toolName, toolInput, findings);
|
|
146
154
|
}
|
|
147
155
|
|
|
148
|
-
const ext = path.extname(filePath).slice(1);
|
|
149
156
|
if (SOURCE_EXTENSIONS.has(ext)) {
|
|
150
157
|
if (toolName === 'Edit') {
|
|
151
158
|
checkLocDelta(toolInput, filePath, findings);
|
|
@@ -154,6 +161,11 @@ function processSingleEdit(toolName, toolInput, emitFn, skipArray) {
|
|
|
154
161
|
}
|
|
155
162
|
}
|
|
156
163
|
|
|
164
|
+
checkSecurityPatterns(toolName, toolInput, filePath, findings);
|
|
165
|
+
if (ext === 'js' || ext === 'ts' || ext === 'jsx' || ext === 'tsx' || ext === 'mjs' || ext === 'cjs') {
|
|
166
|
+
checkLinter(filePath, findings);
|
|
167
|
+
}
|
|
168
|
+
|
|
157
169
|
checkLivingDocNudge(filePath, findings);
|
|
158
170
|
|
|
159
171
|
if (ext !== 'md') {
|
|
@@ -238,6 +250,58 @@ function checkNewFileSize(toolInput, filePath, findings) {
|
|
|
238
250
|
}
|
|
239
251
|
}
|
|
240
252
|
|
|
253
|
+
function checkSecurityPatterns(toolName, toolInput, filePath, findings) {
|
|
254
|
+
var target = toolName === 'Edit' ? (toolInput.new_string || '') : (toolInput.content || '');
|
|
255
|
+
if (!target) return;
|
|
256
|
+
|
|
257
|
+
if (SECURITY_PATTERNS.patterns) {
|
|
258
|
+
SECURITY_PATTERNS.patterns.forEach(function (p) {
|
|
259
|
+
try {
|
|
260
|
+
var regex = new RegExp(p.regex, 'ig');
|
|
261
|
+
if (regex.test(target)) {
|
|
262
|
+
findings.push('[ASC Security] ' + p.message);
|
|
263
|
+
}
|
|
264
|
+
} catch (_) {}
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
var basename = path.basename(filePath);
|
|
269
|
+
if (SECURITY_PATTERNS.fileSpecific && SECURITY_PATTERNS.fileSpecific[basename]) {
|
|
270
|
+
var spec = SECURITY_PATTERNS.fileSpecific[basename];
|
|
271
|
+
try {
|
|
272
|
+
var regex = new RegExp(spec.require, 'g');
|
|
273
|
+
if (target.trim().length > 0 && !regex.test(target)) {
|
|
274
|
+
findings.push('[ASC Security] ' + spec.message);
|
|
275
|
+
}
|
|
276
|
+
} catch (_) {}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function checkLinter(filePath, findings) {
|
|
281
|
+
try {
|
|
282
|
+
var cwd = process.cwd();
|
|
283
|
+
var hasEslint = fs.existsSync(path.join(cwd, '.eslintrc.json')) ||
|
|
284
|
+
fs.existsSync(path.join(cwd, '.eslintrc.js')) ||
|
|
285
|
+
fs.existsSync(path.join(cwd, 'eslint.config.js')) ||
|
|
286
|
+
(fs.existsSync(path.join(cwd, 'package.json')) && fs.readFileSync(path.join(cwd, 'package.json'), 'utf8').includes('eslintConfig'));
|
|
287
|
+
|
|
288
|
+
if (hasEslint) {
|
|
289
|
+
var execSync = require('child_process').execSync;
|
|
290
|
+
execSync('npx eslint "' + filePath + '" --format json', { cwd: cwd, stdio: 'pipe' });
|
|
291
|
+
}
|
|
292
|
+
} catch (error) {
|
|
293
|
+
if (error.stdout) {
|
|
294
|
+
try {
|
|
295
|
+
var out = JSON.parse(error.stdout.toString());
|
|
296
|
+
if (Array.isArray(out) && out.length > 0 && out[0].messages && out[0].messages.length > 0) {
|
|
297
|
+
var firstErr = out[0].messages[0];
|
|
298
|
+
findings.push('[ASC Linter] ' + firstErr.message + ' at line ' + firstErr.line + '.');
|
|
299
|
+
}
|
|
300
|
+
} catch (_) {}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
241
305
|
function checkLivingDocNudge(filePath, findings) {
|
|
242
306
|
var lower = filePath.toLowerCase();
|
|
243
307
|
if (lower.includes('schema') || lower.includes('migration') || lower.includes('model') || lower.includes('prisma')) {
|
|
@@ -38,11 +38,12 @@ Format:
|
|
|
38
38
|
|
|
39
39
|
1. On approval of Phase 1, update `workflow-gate.json` phase to `plan`.
|
|
40
40
|
2. Ensure `docs/PRD.md` or feature brief exists.
|
|
41
|
-
3.
|
|
42
|
-
4.
|
|
43
|
-
5.
|
|
44
|
-
6.
|
|
45
|
-
7.
|
|
41
|
+
3. Check if `.github/workflows/asc-quality-gate.yml` exists. If not, include scaffolding it in your plan (must run linter, type-check, and audit) and remind the user to enable Branch Protection.
|
|
42
|
+
4. Create a numbered, step-by-step implementation plan with specific files, functions, and line references.
|
|
43
|
+
5. Include a "Don't Build" list from the research phase.
|
|
44
|
+
6. **Callout: Plan-Reading Illusion.** Ask the user to explicitly verify the plan against the codebase, not just skim it.
|
|
45
|
+
7. Output the plan.
|
|
46
|
+
8. **STOP and wait for user approval.** Do not implement.
|
|
46
47
|
|
|
47
48
|
## Phase 3: Implement
|
|
48
49
|
|
|
@@ -53,6 +53,7 @@ Format:
|
|
|
53
53
|
2. Run Anti Context-Blindness check: verify entities/tables mentioned in `Schema.md` or `Architecture.md` align with proposed code targets.
|
|
54
54
|
3. Build against the approved specs. Apply the ASC decision ladder on every file.
|
|
55
55
|
4. Run the decision ladder: does this need to exist? Does stdlib cover it? One function or full module?
|
|
56
|
+
5. Generate a `.github/workflows/asc-quality-gate.yml` file that runs linter, type-check, and dependency audit on `push` and `pull_request`. Remind the user to enable Branch Protection in GitHub (require status checks to pass) to enforce this gate globally.
|
|
56
57
|
|
|
57
58
|
## Phase 4: Validate
|
|
58
59
|
|
package/gemini-extension.json
CHANGED
package/package.json
CHANGED
package/plugin.yaml
CHANGED