@clear-capabilities/agentic-security-scanner 0.80.0 → 0.84.1
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/dist/178.index.js +1 -1
- package/dist/384.index.js +1 -1
- package/dist/637.index.js +1 -1
- package/dist/838.index.js +1 -1
- package/dist/839.index.js +170 -0
- package/dist/985.index.js +51 -1
- package/dist/agentic-security.mjs +83 -83
- package/dist/agentic-security.mjs.sha256 +1 -1
- package/package.json +3 -3
- package/src/.agentic-security/findings.json +21283 -8189
- package/src/.agentic-security/last-scan.json +21283 -8189
- package/src/.agentic-security/last-scan.json.sig +1 -1
- package/src/.agentic-security/scan-history.json +512 -128
- package/src/.agentic-security/streak.json +3 -3
- package/src/engine.js +41 -0
- package/src/mcp/.agentic-security/findings.json +4 -4
- package/src/mcp/.agentic-security/last-scan.json +4 -4
- package/src/mcp/.agentic-security/last-scan.json.sig +1 -1
- package/src/mcp/.agentic-security/scan-history.json +188 -0
- package/src/mcp/.agentic-security/streak.json +5 -5
- package/src/mcp/tools.js +51 -1
- package/src/posture/.agentic-security/findings.json +17234 -4057
- package/src/posture/.agentic-security/last-scan.json +17234 -4057
- package/src/posture/.agentic-security/last-scan.json.sig +1 -1
- package/src/posture/.agentic-security/scan-history.json +1942 -200
- package/src/posture/.agentic-security/streak.json +3 -3
- package/src/posture/auditor-walkthrough.js +252 -0
- package/src/posture/claude-authorship.js +197 -0
- package/src/posture/compliance-frameworks/.agentic-security/findings.json +80 -0
- package/src/posture/compliance-frameworks/.agentic-security/last-scan.json +80 -0
- package/src/posture/compliance-frameworks/.agentic-security/last-scan.json.sig +1 -0
- package/src/posture/compliance-frameworks/.agentic-security/scan-history.json +90 -0
- package/src/posture/compliance-frameworks/.agentic-security/streak.json +22 -0
- package/src/posture/compliance-frameworks/ccpa.json +32 -0
- package/src/posture/compliance-frameworks/eu-ai-act.json +51 -0
- package/src/posture/compliance-frameworks/gdpr.json +45 -0
- package/src/posture/compliance-frameworks/hipaa-security-rule.json +56 -0
- package/src/posture/compliance-frameworks/nist-ai-600-1.json +51 -0
- package/src/posture/compliance-frameworks/nist-csf-2.json +73 -0
- package/src/posture/compliance-frameworks/owasp-asvs-5.json +79 -0
- package/src/posture/compliance-frameworks/owasp-llm-top-10.json +69 -0
- package/src/posture/cross-repo-memory.js +180 -0
- package/src/posture/dep-add-guard.js +197 -0
- package/src/posture/findings-memory.js +152 -0
- package/src/posture/fix-style-mirror.js +118 -0
- package/src/posture/git-history.js +141 -0
- package/src/posture/intent-context.js +175 -0
- package/src/posture/model-rescan.js +76 -0
- package/src/posture/pattern-propagation.js +39 -0
- package/src/posture/pr-augment.js +234 -0
- package/src/posture/risk-dollars.js +158 -0
- package/src/posture/threat-model-grounding.js +169 -0
- package/src/posture/time-to-fix.js +129 -0
- package/src/posture/triage-memory.js +151 -0
- package/src/posture/triage.js +15 -1
- package/src/posture/watch-mode.js +171 -0
- package/src/posture/workflow-installer.js +231 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
// Watch mode — continuous incremental scan as the developer edits.
|
|
2
|
+
//
|
|
3
|
+
// Spawns a long-running scan watcher that:
|
|
4
|
+
// 1. Subscribes to file-system events under the project root
|
|
5
|
+
// 2. When a file matching the scan glob changes, re-scans incrementally
|
|
6
|
+
// using dataflow/incremental-cache.js (per-file cache hits avoid the
|
|
7
|
+
// full IR build)
|
|
8
|
+
// 3. Diffs against the prior scan to compute a "risk delta" string
|
|
9
|
+
// (added/removed/changed criticals + highs)
|
|
10
|
+
// 4. Writes the delta to .agentic-security/watch-status.{md,json}
|
|
11
|
+
//
|
|
12
|
+
// The Claude Code statusline / a chat command can poll watch-status.md
|
|
13
|
+
// (cheap file read) to surface the delta inline without re-running anything.
|
|
14
|
+
//
|
|
15
|
+
// Implementation is deliberately node-only — no chokidar dep, uses
|
|
16
|
+
// fs.promises.watch (Node ≥ 20). Debounces to 350ms.
|
|
17
|
+
//
|
|
18
|
+
// Lifecycle: start() returns an AbortController-like handle. The caller
|
|
19
|
+
// (a /watch slash command or a long-running daemon spawn) is responsible
|
|
20
|
+
// for keeping the process alive; this module is the pure logic.
|
|
21
|
+
|
|
22
|
+
import * as fs from 'node:fs/promises';
|
|
23
|
+
import * as fsSync from 'node:fs';
|
|
24
|
+
import * as path from 'node:path';
|
|
25
|
+
|
|
26
|
+
const STATE = '.agentic-security';
|
|
27
|
+
const STATUS_MD = 'watch-status.md';
|
|
28
|
+
const STATUS_JSON = 'watch-status.json';
|
|
29
|
+
const DEBOUNCE_MS = 350;
|
|
30
|
+
const MAX_BURST = 50; // ignore beyond this # of rapid events
|
|
31
|
+
|
|
32
|
+
const SCAN_EXT_RE = /\.(?:[jt]sx?|mjs|cjs|py|java|kt|go|rb|php|cs|c|cc|cpp|h|hpp|rs|sol|vy|swift|dart|toml|yml|yaml|json|tf|tfvars|bicep)$/i;
|
|
33
|
+
const IGNORE_DIR_RE = /(?:^|\/)(?:\.git|node_modules|\.bench-cache|dist|build|\.next|coverage|\.agentic-security)(?:$|\/)/;
|
|
34
|
+
|
|
35
|
+
function _isScanable(rel) {
|
|
36
|
+
if (!rel || IGNORE_DIR_RE.test(rel)) return false;
|
|
37
|
+
return SCAN_EXT_RE.test(rel);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function _readJsonSafe(fp) {
|
|
41
|
+
try { return JSON.parse(fsSync.readFileSync(fp, 'utf8')); } catch { return null; }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function _sevRank(s) { return ['info', 'low', 'medium', 'high', 'critical'].indexOf(s) + 1; }
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Pure delta computation between two finding arrays. Same logic
|
|
48
|
+
* baseline-compare uses, surfaced as a single ASCII status line.
|
|
49
|
+
*/
|
|
50
|
+
export function computeDelta(prevFindings, currFindings) {
|
|
51
|
+
const key = (f) => `${f.file || ''}::${f.line || 0}::${f.family || f.parser || ''}`;
|
|
52
|
+
const prev = new Map();
|
|
53
|
+
for (const f of prevFindings || []) prev.set(key(f), f);
|
|
54
|
+
const cur = new Map();
|
|
55
|
+
for (const f of currFindings || []) cur.set(key(f), f);
|
|
56
|
+
const added = [], removed = [];
|
|
57
|
+
for (const [k, f] of cur) if (!prev.has(k)) added.push(f);
|
|
58
|
+
for (const [k, f] of prev) if (!cur.has(k)) removed.push(f);
|
|
59
|
+
const newCrit = added.filter(f => f.severity === 'critical').length;
|
|
60
|
+
const newHigh = added.filter(f => f.severity === 'high').length;
|
|
61
|
+
const fixedCrit = removed.filter(f => f.severity === 'critical').length;
|
|
62
|
+
const fixedHigh = removed.filter(f => f.severity === 'high').length;
|
|
63
|
+
return {
|
|
64
|
+
addedCount: added.length, removedCount: removed.length,
|
|
65
|
+
newCritical: newCrit, newHigh, fixedCritical: fixedCrit, fixedHigh,
|
|
66
|
+
added, removed,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Render a one-line status string for the Claude Code statusline.
|
|
72
|
+
*/
|
|
73
|
+
export function renderStatusLine(delta) {
|
|
74
|
+
const parts = [];
|
|
75
|
+
if (delta.newCritical) parts.push(`🛑 +${delta.newCritical} crit`);
|
|
76
|
+
if (delta.newHigh) parts.push(`⚠️ +${delta.newHigh} high`);
|
|
77
|
+
if (delta.fixedCritical) parts.push(`✅ -${delta.fixedCritical} crit`);
|
|
78
|
+
if (delta.fixedHigh) parts.push(`✅ -${delta.fixedHigh} high`);
|
|
79
|
+
if (!parts.length && (delta.addedCount + delta.removedCount) === 0) return 'agentic-security: clean';
|
|
80
|
+
if (!parts.length) return `agentic-security: +${delta.addedCount} / -${delta.removedCount}`;
|
|
81
|
+
return 'agentic-security: ' + parts.join(' · ');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Persist watch-status.{md,json}. Cheap atomic write (write tmp, rename).
|
|
86
|
+
*/
|
|
87
|
+
export function persistStatus(scanRoot, delta) {
|
|
88
|
+
const dir = path.join(scanRoot, STATE);
|
|
89
|
+
try { fsSync.mkdirSync(dir, { recursive: true }); } catch {}
|
|
90
|
+
const status = {
|
|
91
|
+
ts: new Date().toISOString(),
|
|
92
|
+
line: renderStatusLine(delta),
|
|
93
|
+
delta: {
|
|
94
|
+
addedCount: delta.addedCount, removedCount: delta.removedCount,
|
|
95
|
+
newCritical: delta.newCritical, newHigh: delta.newHigh,
|
|
96
|
+
fixedCritical: delta.fixedCritical, fixedHigh: delta.fixedHigh,
|
|
97
|
+
},
|
|
98
|
+
addedTop5: (delta.added || []).slice(0, 5).map(f => ({
|
|
99
|
+
file: f.file, line: f.line, family: f.family, severity: f.severity, vuln: f.vuln,
|
|
100
|
+
})),
|
|
101
|
+
};
|
|
102
|
+
const jsonPath = path.join(dir, STATUS_JSON);
|
|
103
|
+
const mdPath = path.join(dir, STATUS_MD);
|
|
104
|
+
try { fsSync.writeFileSync(jsonPath, JSON.stringify(status, null, 2)); } catch {}
|
|
105
|
+
const md = [
|
|
106
|
+
`# Watch status — ${status.ts.slice(11, 19)} UTC`,
|
|
107
|
+
'',
|
|
108
|
+
status.line,
|
|
109
|
+
'',
|
|
110
|
+
];
|
|
111
|
+
if (status.addedTop5.length) {
|
|
112
|
+
md.push('## New findings');
|
|
113
|
+
for (const f of status.addedTop5) {
|
|
114
|
+
md.push(`- **[${(f.severity || '?').toUpperCase()}]** ${f.vuln || f.family || 'finding'} — \`${f.file}:${f.line}\``);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
try { fsSync.writeFileSync(mdPath, md.join('\n')); } catch {}
|
|
118
|
+
return status;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Read the latest watch-status (returns null if none).
|
|
123
|
+
*/
|
|
124
|
+
export function readStatus(scanRoot) {
|
|
125
|
+
return _readJsonSafe(path.join(scanRoot, STATE, STATUS_JSON));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Subscribe to FS events and call onChange(absPath, eventType) for each
|
|
130
|
+
* matching event. Debounces bursts. Returns a controller with stop().
|
|
131
|
+
*
|
|
132
|
+
* The actual incremental scan call lives in the caller — this module
|
|
133
|
+
* stays pure for testability.
|
|
134
|
+
*/
|
|
135
|
+
export async function watchProject(scanRoot, onChange, opts = {}) {
|
|
136
|
+
if (process.env.AGENTIC_SECURITY_NO_WATCH === '1') return { stop: async () => {}, _disabled: true };
|
|
137
|
+
const recursive = opts.recursive !== false;
|
|
138
|
+
const ac = new AbortController();
|
|
139
|
+
let timer = null;
|
|
140
|
+
const pending = new Set();
|
|
141
|
+
const flush = () => {
|
|
142
|
+
timer = null;
|
|
143
|
+
if (pending.size > MAX_BURST) { pending.clear(); return; }
|
|
144
|
+
const batch = Array.from(pending);
|
|
145
|
+
pending.clear();
|
|
146
|
+
try { onChange(batch); } catch {}
|
|
147
|
+
};
|
|
148
|
+
let stopped = false;
|
|
149
|
+
(async () => {
|
|
150
|
+
try {
|
|
151
|
+
for await (const evt of fs.watch(scanRoot, { recursive, signal: ac.signal })) {
|
|
152
|
+
if (stopped) break;
|
|
153
|
+
const rel = String(evt.filename || '').replace(/\\/g, '/');
|
|
154
|
+
if (!_isScanable(rel)) continue;
|
|
155
|
+
pending.add(path.join(scanRoot, rel));
|
|
156
|
+
if (timer) clearTimeout(timer);
|
|
157
|
+
timer = setTimeout(flush, DEBOUNCE_MS);
|
|
158
|
+
}
|
|
159
|
+
} catch (e) {
|
|
160
|
+
if (e && e.name !== 'AbortError') {
|
|
161
|
+
// Surface but don't crash — caller decides how to handle.
|
|
162
|
+
try { onChange([], e); } catch {}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
})();
|
|
166
|
+
return {
|
|
167
|
+
stop: async () => { stopped = true; ac.abort(); if (timer) clearTimeout(timer); },
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export const _internals = { _isScanable, SCAN_EXT_RE, IGNORE_DIR_RE };
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
// Workflow installer — detect the project type and emit pre-commit
|
|
2
|
+
// hook scripts + CI YAML files tuned to the repo.
|
|
3
|
+
//
|
|
4
|
+
// Two functions:
|
|
5
|
+
//
|
|
6
|
+
// detectProject(scanRoot)
|
|
7
|
+
// Returns { lang, pm, ciProvider, hookManager } based on file probes.
|
|
8
|
+
//
|
|
9
|
+
// buildHookConfig(scanRoot, opts)
|
|
10
|
+
// Returns { files: { path: content, ... } } to write — pre-commit
|
|
11
|
+
// hook configuration in whichever style the project already uses
|
|
12
|
+
// (husky / pre-commit / lefthook / native git hooks).
|
|
13
|
+
//
|
|
14
|
+
// buildCiConfig(scanRoot, opts)
|
|
15
|
+
// Returns { files: { path: content, ... } } for the chosen CI
|
|
16
|
+
// provider (github-actions / gitlab-ci / circleci / native).
|
|
17
|
+
//
|
|
18
|
+
// Pure render — caller writes the files and runs install commands.
|
|
19
|
+
// No subprocess calls happen inside this module.
|
|
20
|
+
|
|
21
|
+
import * as fs from 'node:fs';
|
|
22
|
+
import * as path from 'node:path';
|
|
23
|
+
|
|
24
|
+
function _exists(p) { return fs.existsSync(p); }
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Project-type detection.
|
|
28
|
+
*/
|
|
29
|
+
export function detectProject(scanRoot) {
|
|
30
|
+
const has = (rel) => _exists(path.join(scanRoot, rel));
|
|
31
|
+
const lang =
|
|
32
|
+
has('package.json') ? 'node' :
|
|
33
|
+
has('pyproject.toml') || has('requirements.txt') ? 'python' :
|
|
34
|
+
has('Cargo.toml') ? 'rust' :
|
|
35
|
+
has('go.mod') ? 'go' :
|
|
36
|
+
has('pom.xml') || has('build.gradle') || has('build.gradle.kts') ? 'java' :
|
|
37
|
+
has('Gemfile') ? 'ruby' :
|
|
38
|
+
has('composer.json') ? 'php' :
|
|
39
|
+
'unknown';
|
|
40
|
+
const pm =
|
|
41
|
+
has('yarn.lock') ? 'yarn' :
|
|
42
|
+
has('pnpm-lock.yaml') ? 'pnpm' :
|
|
43
|
+
has('package-lock.json') ? 'npm' :
|
|
44
|
+
has('poetry.lock') ? 'poetry' :
|
|
45
|
+
has('Pipfile.lock') ? 'pipenv' :
|
|
46
|
+
null;
|
|
47
|
+
const ciProvider =
|
|
48
|
+
has('.github/workflows') ? 'github-actions' :
|
|
49
|
+
has('.gitlab-ci.yml') ? 'gitlab-ci' :
|
|
50
|
+
has('.circleci/config.yml') ? 'circleci' :
|
|
51
|
+
has('Jenkinsfile') ? 'jenkins' :
|
|
52
|
+
null;
|
|
53
|
+
const hookManager =
|
|
54
|
+
has('.husky') ? 'husky' :
|
|
55
|
+
has('.pre-commit-config.yaml') ? 'pre-commit' :
|
|
56
|
+
has('lefthook.yml') || has('.lefthook.yml') ? 'lefthook' :
|
|
57
|
+
has('.git/hooks') ? 'native' :
|
|
58
|
+
null;
|
|
59
|
+
return { lang, pm, ciProvider, hookManager };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Build pre-commit hook config tuned to the project's chosen hook manager.
|
|
64
|
+
*
|
|
65
|
+
* opts:
|
|
66
|
+
* severity: 'critical' | 'high' | 'medium' (default 'critical')
|
|
67
|
+
* diffOnly: true (default) — scan only changed files
|
|
68
|
+
*/
|
|
69
|
+
export function buildHookConfig(scanRoot, opts = {}) {
|
|
70
|
+
const sev = opts.severity || 'critical';
|
|
71
|
+
const diffOnly = opts.diffOnly !== false;
|
|
72
|
+
const detected = detectProject(scanRoot);
|
|
73
|
+
const cmd = `npx --no-install agentic-security scan ${diffOnly ? '--diff' : ''} --fail-on ${sev}`.replace(/\s+/g, ' ').trim();
|
|
74
|
+
|
|
75
|
+
const files = {};
|
|
76
|
+
const manager = detected.hookManager || (detected.lang === 'node' ? 'husky' : 'pre-commit');
|
|
77
|
+
|
|
78
|
+
if (manager === 'husky') {
|
|
79
|
+
files['.husky/pre-commit'] = [
|
|
80
|
+
'#!/usr/bin/env sh',
|
|
81
|
+
'. "$(dirname -- "$0")/_/husky.sh"',
|
|
82
|
+
'',
|
|
83
|
+
'# agentic-security: refuse the commit if any new critical finding lands',
|
|
84
|
+
cmd,
|
|
85
|
+
'',
|
|
86
|
+
].join('\n');
|
|
87
|
+
} else if (manager === 'pre-commit') {
|
|
88
|
+
const cfgPath = '.pre-commit-config.yaml';
|
|
89
|
+
const existing = _exists(path.join(scanRoot, cfgPath)) ? fs.readFileSync(path.join(scanRoot, cfgPath), 'utf8') : 'repos:\n';
|
|
90
|
+
const hookYaml = [
|
|
91
|
+
' - repo: local',
|
|
92
|
+
' hooks:',
|
|
93
|
+
' - id: agentic-security',
|
|
94
|
+
' name: agentic-security scan (diff)',
|
|
95
|
+
' entry: ' + cmd,
|
|
96
|
+
' language: system',
|
|
97
|
+
' pass_filenames: false',
|
|
98
|
+
' stages: [pre-commit]',
|
|
99
|
+
'',
|
|
100
|
+
].join('\n');
|
|
101
|
+
if (!/agentic-security/.test(existing)) files[cfgPath] = existing.trim() + '\n\n' + hookYaml;
|
|
102
|
+
} else if (manager === 'lefthook') {
|
|
103
|
+
const cfgPath = 'lefthook.yml';
|
|
104
|
+
const existing = _exists(path.join(scanRoot, cfgPath)) ? fs.readFileSync(path.join(scanRoot, cfgPath), 'utf8') : 'pre-commit:\n commands:\n';
|
|
105
|
+
const hookYaml = [
|
|
106
|
+
' commands:',
|
|
107
|
+
' agentic-security:',
|
|
108
|
+
' run: ' + cmd,
|
|
109
|
+
'',
|
|
110
|
+
].join('\n');
|
|
111
|
+
if (!/agentic-security/.test(existing)) files[cfgPath] = existing.trim() + '\n' + hookYaml;
|
|
112
|
+
} else {
|
|
113
|
+
// Native git hooks fallback.
|
|
114
|
+
files['.git/hooks/pre-commit'] = [
|
|
115
|
+
'#!/usr/bin/env sh',
|
|
116
|
+
'# agentic-security: pre-commit security scan',
|
|
117
|
+
cmd,
|
|
118
|
+
'',
|
|
119
|
+
].join('\n');
|
|
120
|
+
}
|
|
121
|
+
return { manager, files };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Build CI workflow config for the chosen provider.
|
|
126
|
+
*
|
|
127
|
+
* opts:
|
|
128
|
+
* provider: 'github-actions' | 'gitlab-ci' | 'circleci' | 'auto'
|
|
129
|
+
* schedule: cron string for weekly full scan (default 'Mon 09:00 UTC')
|
|
130
|
+
* prSeverityFloor: 'critical' (default)
|
|
131
|
+
*/
|
|
132
|
+
export function buildCiConfig(scanRoot, opts = {}) {
|
|
133
|
+
const detected = detectProject(scanRoot);
|
|
134
|
+
const provider = opts.provider === 'auto' || !opts.provider ? (detected.ciProvider || 'github-actions') : opts.provider;
|
|
135
|
+
const sev = opts.prSeverityFloor || 'critical';
|
|
136
|
+
|
|
137
|
+
const files = {};
|
|
138
|
+
if (provider === 'github-actions') {
|
|
139
|
+
files['.github/workflows/agentic-security.yml'] = _githubActions(sev);
|
|
140
|
+
} else if (provider === 'gitlab-ci') {
|
|
141
|
+
files['.gitlab-ci-agentic-security.yml'] = _gitlabCi(sev);
|
|
142
|
+
} else if (provider === 'circleci') {
|
|
143
|
+
files['.circleci/agentic-security.yml'] = _circleCi(sev);
|
|
144
|
+
} else {
|
|
145
|
+
files['ci/agentic-security.sh'] = _nativeShell(sev);
|
|
146
|
+
}
|
|
147
|
+
return { provider, files };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function _githubActions(sev) {
|
|
151
|
+
return [
|
|
152
|
+
'name: agentic-security',
|
|
153
|
+
'',
|
|
154
|
+
'on:',
|
|
155
|
+
' pull_request:',
|
|
156
|
+
' branches: [main, master, develop]',
|
|
157
|
+
' push:',
|
|
158
|
+
' branches: [main, master]',
|
|
159
|
+
' schedule:',
|
|
160
|
+
' - cron: "0 9 * * 1" # Monday 09:00 UTC — full weekly scan',
|
|
161
|
+
' workflow_dispatch:',
|
|
162
|
+
'',
|
|
163
|
+
'permissions:',
|
|
164
|
+
' contents: read',
|
|
165
|
+
' security-events: write',
|
|
166
|
+
'',
|
|
167
|
+
'jobs:',
|
|
168
|
+
' scan:',
|
|
169
|
+
' runs-on: ubuntu-latest',
|
|
170
|
+
' steps:',
|
|
171
|
+
' - uses: actions/checkout@v5',
|
|
172
|
+
' with:',
|
|
173
|
+
' fetch-depth: 0 # full history for diff baselining',
|
|
174
|
+
" - uses: actions/setup-node@v5",
|
|
175
|
+
" with: { node-version: 'lts/*' }",
|
|
176
|
+
' - name: Run agentic-security',
|
|
177
|
+
' run: |',
|
|
178
|
+
` npx -y @clear-capabilities/agentic-security-scanner scan --fail-on ${sev} --sarif > security.sarif`,
|
|
179
|
+
' - uses: github/codeql-action/upload-sarif@v3',
|
|
180
|
+
' if: always()',
|
|
181
|
+
' with: { sarif_file: security.sarif }',
|
|
182
|
+
'',
|
|
183
|
+
].join('\n');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function _gitlabCi(sev) {
|
|
187
|
+
return [
|
|
188
|
+
'agentic-security:',
|
|
189
|
+
' image: node:lts',
|
|
190
|
+
' rules:',
|
|
191
|
+
' - if: $CI_PIPELINE_SOURCE == "merge_request_event"',
|
|
192
|
+
' - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH',
|
|
193
|
+
' script:',
|
|
194
|
+
` - npx -y @clear-capabilities/agentic-security-scanner scan --fail-on ${sev} --sarif > security.sarif`,
|
|
195
|
+
' artifacts:',
|
|
196
|
+
' paths: [security.sarif]',
|
|
197
|
+
' reports: { sast: security.sarif }',
|
|
198
|
+
'',
|
|
199
|
+
].join('\n');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function _circleCi(sev) {
|
|
203
|
+
return [
|
|
204
|
+
'version: 2.1',
|
|
205
|
+
'jobs:',
|
|
206
|
+
' agentic-security:',
|
|
207
|
+
' docker:',
|
|
208
|
+
' - image: cimg/node:lts',
|
|
209
|
+
' steps:',
|
|
210
|
+
' - checkout',
|
|
211
|
+
' - run:',
|
|
212
|
+
' name: agentic-security scan',
|
|
213
|
+
` command: npx -y @clear-capabilities/agentic-security-scanner scan --fail-on ${sev}`,
|
|
214
|
+
'workflows:',
|
|
215
|
+
' security:',
|
|
216
|
+
' jobs: [agentic-security]',
|
|
217
|
+
'',
|
|
218
|
+
].join('\n');
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function _nativeShell(sev) {
|
|
222
|
+
return [
|
|
223
|
+
'#!/usr/bin/env bash',
|
|
224
|
+
'set -euo pipefail',
|
|
225
|
+
'',
|
|
226
|
+
`npx -y @clear-capabilities/agentic-security-scanner scan --fail-on ${sev}`,
|
|
227
|
+
'',
|
|
228
|
+
].join('\n');
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export const _internals = { _exists, _githubActions, _gitlabCi, _circleCi, _nativeShell };
|