wize-dev-kit 0.5.0 → 0.6.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.
Files changed (35) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/package.json +1 -1
  3. package/src/security-overlay/_shared/allowlist.js +154 -0
  4. package/src/security-overlay/_shared/cli-runner.js +87 -0
  5. package/src/security-overlay/_shared/cvss.js +108 -0
  6. package/src/security-overlay/_shared/detect.js +125 -0
  7. package/src/security-overlay/_shared/install-script.js +205 -0
  8. package/src/security-overlay/_shared/invoke-phase.js +86 -0
  9. package/src/security-overlay/_shared/owasp.js +56 -0
  10. package/src/security-overlay/_shared/partial.js +225 -0
  11. package/src/security-overlay/_shared/preflight.js +175 -0
  12. package/src/security-overlay/_shared/scope-gate.js +172 -0
  13. package/src/security-overlay/_shared/scope-parser.js +120 -0
  14. package/src/security-overlay/agents/red-teamer/agent.yaml +51 -0
  15. package/src/security-overlay/agents/red-teamer/persona.md +43 -0
  16. package/src/security-overlay/data/common.txt +115 -0
  17. package/src/security-overlay/data/owasp-top10.json +15 -0
  18. package/src/security-overlay/data/tool-allowlist.json +31 -0
  19. package/src/security-overlay/skills/wize-sec-enumerate/scripts/run-enumerate.js +180 -0
  20. package/src/security-overlay/skills/wize-sec-enumerate/skill.md +32 -0
  21. package/src/security-overlay/skills/wize-sec-exploit/data/common.txt +117 -0
  22. package/src/security-overlay/skills/wize-sec-exploit/scripts/run-ffuf.js +147 -0
  23. package/src/security-overlay/skills/wize-sec-exploit/scripts/run-nikto.js +145 -0
  24. package/src/security-overlay/skills/wize-sec-exploit/scripts/run-nuclei.js +176 -0
  25. package/src/security-overlay/skills/wize-sec-exploit/scripts/run-sqlmap.js +139 -0
  26. package/src/security-overlay/skills/wize-sec-pentest/scripts/run-pipeline.js +157 -0
  27. package/src/security-overlay/skills/wize-sec-pentest/skill.md +52 -0
  28. package/src/security-overlay/skills/wize-sec-recon/scripts/run-gitleaks.js +139 -0
  29. package/src/security-overlay/skills/wize-sec-recon/scripts/run-osv.js +227 -0
  30. package/src/security-overlay/skills/wize-sec-recon/scripts/run-recon.js +162 -0
  31. package/src/security-overlay/skills/wize-sec-recon/skill.md +35 -0
  32. package/src/security-overlay/skills/wize-sec-report/scripts/render-report.js +999 -0
  33. package/tools/installer/onboarding.js +1 -0
  34. package/tools/installer/render-shared.js +5 -1
  35. package/tools/installer/wize-cli.js +8 -1
@@ -0,0 +1,175 @@
1
+ 'use strict';
2
+
3
+ // preflight.js — detects the host environment (OS/arch/package manager)
4
+ // and which tools from data/tool-allowlist.json are installed.
5
+ //
6
+ // Test hook: when WIZE_SEC_PREFLIGHT_OS, WIZE_SEC_PREFLIGHT_PM, and
7
+ // WIZE_SEC_PREFLIGHT_TOOLS (JSON) env vars are set, the real probes
8
+ // are skipped and the values are returned directly. This lets tests
9
+ // simulate Mac/Linux/Windows-WSL without actually running `which` etc.
10
+
11
+ const fs = require('node:fs');
12
+ const path = require('node:path');
13
+ const os = require('node:os');
14
+ const { execFileSync, spawnSync } = require('node:child_process');
15
+
16
+ const ALLOWLIST_PATH = path.join(__dirname, '..', 'data', 'tool-allowlist.json');
17
+
18
+ function readToolNames() {
19
+ try {
20
+ const data = JSON.parse(fs.readFileSync(ALLOWLIST_PATH, 'utf8'));
21
+ // Skip _schema and any non-array fields.
22
+ return Object.keys(data).filter(k => k !== '_schema' && Array.isArray(data[k]));
23
+ } catch (_) {
24
+ return [];
25
+ }
26
+ }
27
+
28
+ function detectOS() {
29
+ if (process.env.WIZE_SEC_PREFLIGHT_OS) return process.env.WIZE_SEC_PREFLIGHT_OS;
30
+ const platform = os.platform();
31
+ if (platform === 'linux') {
32
+ // Detect WSL by reading /proc/version for "Microsoft" or "WSL".
33
+ try {
34
+ const v = fs.readFileSync('/proc/version', 'utf8');
35
+ if (/microsoft|wsl/i.test(v)) return 'wsl';
36
+ } catch (_) { /* not linux */ }
37
+ return 'linux';
38
+ }
39
+ if (platform === 'darwin') return 'darwin';
40
+ if (platform === 'win32') return 'win32';
41
+ return 'linux';
42
+ }
43
+
44
+ function detectArch() {
45
+ if (process.env.WIZE_SEC_PREFLIGHT_ARCH) return process.env.WIZE_SEC_PREFLIGHT_ARCH;
46
+ return process.arch; // 'x64' | 'arm64' | 'ia32' etc.
47
+ }
48
+
49
+ function detectPackageManager(os) {
50
+ if (process.env.WIZE_SEC_PREFLIGHT_PM) return process.env.WIZE_SEC_PREFLIGHT_PM;
51
+ // Check for the most common PMs in order.
52
+ const candidates = {
53
+ linux: ['apt', 'dnf', 'pacman', 'zypper', 'apk'],
54
+ wsl: ['apt', 'dnf', 'pacman'],
55
+ darwin: ['brew'],
56
+ win32: ['scoop', 'chocolatey']
57
+ };
58
+ const list = candidates[os] || [];
59
+ for (const pm of list) {
60
+ const cmds = { apt: 'apt', dnf: 'dnf', pacman: 'pacman', zypper: 'zypper', apk: 'apk', brew: 'brew', scoop: 'scoop', chocolatey: 'choco' }[pm];
61
+ try {
62
+ execFileSync(cmds, ['--version'], { stdio: 'ignore' });
63
+ return pm;
64
+ } catch (_) { /* not present */ }
65
+ }
66
+ return 'none';
67
+ }
68
+
69
+ function whichCommand(os) {
70
+ // 'command -v' on POSIX, 'where' on Windows.
71
+ if (os === 'win32') return 'where';
72
+ return 'command';
73
+ }
74
+
75
+ function whichArg(os) {
76
+ if (os === 'win32') return [];
77
+ return ['-v'];
78
+ }
79
+
80
+ function probeWhich(name, os) {
81
+ try {
82
+ const r = spawnSync(whichCommand(os), [...whichArg(os), name], { encoding: 'utf8', timeout: 2000 });
83
+ if (r.status === 0 && r.stdout) {
84
+ return r.stdout.split('\n')[0].trim();
85
+ }
86
+ } catch (_) { /* not present */ }
87
+ return null;
88
+ }
89
+
90
+ function probeVersion(binPath, os) {
91
+ // Try common version flags. Each is a separate probe; failure is non-fatal.
92
+ const flags = ['--version', '-version', '-V', 'version'];
93
+ for (const f of flags) {
94
+ try {
95
+ const out = execFileSync(binPath, [f], { encoding: 'utf8', timeout: 2000, stdio: ['ignore', 'pipe', 'ignore'] });
96
+ const first = (out || '').split('\n')[0].trim();
97
+ if (first) return first.slice(0, 120);
98
+ } catch (_) { /* try next */ }
99
+ }
100
+ return null;
101
+ }
102
+
103
+ function detectTools(os) {
104
+ const names = readToolNames();
105
+ const tools = {};
106
+ // Initialize all tools as missing.
107
+ for (const n of names) tools[n] = { present: false };
108
+ // Test hook: parse the JSON env var. The hook marks the listed tools as
109
+ // present; everything else stays missing.
110
+ if (process.env.WIZE_SEC_PREFLIGHT_TOOLS) {
111
+ try {
112
+ const m = JSON.parse(process.env.WIZE_SEC_PREFLIGHT_TOOLS);
113
+ for (const [name, path] of Object.entries(m)) {
114
+ if (!(name in tools)) tools[name] = { present: !!path, path: path || undefined, version: null };
115
+ else {
116
+ tools[name] = { present: !!path, path: path || undefined, version: null };
117
+ }
118
+ }
119
+ } catch (_) { /* fall through to real detection */ }
120
+ return tools;
121
+ }
122
+ // Real detection.
123
+ for (const name of names) {
124
+ const p = probeWhich(name, os);
125
+ if (!p) {
126
+ tools[name] = { present: false };
127
+ } else {
128
+ tools[name] = { present: true, path: p, version: probeVersion(p, os) };
129
+ }
130
+ }
131
+ return tools;
132
+ }
133
+
134
+ function runPreflight(opts = {}) {
135
+ const os_ = detectOS();
136
+ const arch = detectArch();
137
+ const pm = detectPackageManager(os_);
138
+ const tools = detectTools(os_);
139
+ const missing = Object.entries(tools).filter(([, v]) => !v.present).map(([k]) => k);
140
+ return {
141
+ os: os_,
142
+ arch,
143
+ packageManager: pm,
144
+ tools,
145
+ missing,
146
+ node: process.version,
147
+ nodePath: process.execPath
148
+ };
149
+ }
150
+
151
+ function formatReport(p) {
152
+ const present = Object.entries(p.tools).filter(([, v]) => v.present);
153
+ const lines = [];
154
+ lines.push(`OS: ${p.os} (${p.arch})`);
155
+ lines.push(`Package manager: ${p.packageManager || 'none'}`);
156
+ lines.push(`Node: ${p.node}`);
157
+ lines.push('');
158
+ lines.push(`Tools: ${present.length} present, ${p.missing.length} missing`);
159
+ if (present.length) {
160
+ lines.push(' present:');
161
+ for (const [name, info] of present) {
162
+ const v = info.version ? ` — ${info.version}` : '';
163
+ lines.push(` ✓ ${name}${v}`);
164
+ }
165
+ }
166
+ if (p.missing.length) {
167
+ lines.push(' missing:');
168
+ for (const name of p.missing) lines.push(` ✗ ${name}`);
169
+ lines.push('');
170
+ lines.push('Run the install script (see .wize/security/install-pentest-tools.sh) to add the missing tools.');
171
+ }
172
+ return lines.join('\n');
173
+ }
174
+
175
+ module.exports = { runPreflight, formatReport, readToolNames };
@@ -0,0 +1,172 @@
1
+ 'use strict';
2
+
3
+ // scope-gate.js — single point that decides whether an offensive tool may run
4
+ // against a given target. ADR-001: this module is THE gate; skills must call
5
+ // assertTargetInScope before any execFile.
6
+ //
7
+ // Refusals (returns false) are logged to .wize/security/.refusals.log with
8
+ // ISO-8601 timestamp + target + reason. Scope validation errors (ScopeError)
9
+ // propagate — they signal an invalid scope, not a refused action.
10
+
11
+ const fs = require('node:fs');
12
+ const path = require('node:path');
13
+
14
+ const {
15
+ parseScope,
16
+ validateScope,
17
+ ScopeError
18
+ } = require('./scope-parser.js');
19
+
20
+ const REFSUAL_LOG_FILENAME = '.refusals.log';
21
+
22
+ // --- body parsing --------------------------------------------------------
23
+
24
+ // Parse the body of a scope.md into a structured allowlist. We accept the
25
+ // shape defined in ADR-002:
26
+ //
27
+ // ## allowlist
28
+ // hosts:
29
+ // - localhost
30
+ // - 127.0.0.1
31
+ // urls:
32
+ // - https://staging.example.internal/api/
33
+ // paths:
34
+ // - /api
35
+ //
36
+ // Zero-dep: each list block is matched line-by-line under its `## allowlist`
37
+ // heading until the next `##` heading or EOF. Empty / missing blocks yield
38
+ // empty arrays (which makes EVERY target fail the allowlist — fail-closed).
39
+ function parseAllowlist(body) {
40
+ const out = { hosts: [], urls: [], paths: [] };
41
+ const lines = String(body || '').split('\n');
42
+ let section = null;
43
+ let key = null;
44
+ for (const line of lines) {
45
+ const h = line.match(/^##\s+([a-zA-Z_][a-zA-Z0-9_-]*)/);
46
+ if (h) {
47
+ section = h[1] === 'allowlist' ? 'allowlist' : null;
48
+ key = null;
49
+ continue;
50
+ }
51
+ if (section !== 'allowlist') continue;
52
+ const km = line.match(/^([a-zA-Z_][a-zA-Z0-9_-]*):\s*$/);
53
+ if (km) {
54
+ const k = km[1];
55
+ if (k === 'hosts' || k === 'urls' || k === 'paths') key = k;
56
+ else key = null;
57
+ continue;
58
+ }
59
+ const lm = line.match(/^\s+-\s+(.+?)\s*$/);
60
+ if (lm && key) {
61
+ out[key].push(lm[1]);
62
+ }
63
+ }
64
+ return out;
65
+ }
66
+
67
+ // --- matching ------------------------------------------------------------
68
+
69
+ function matchHost(allowlist, host) {
70
+ return allowlist.hosts.some(h => h === host);
71
+ }
72
+
73
+ function matchUrl(allowlist, url) {
74
+ // Normalize by stripping trailing slashes so 'http://x/' and 'http://x'
75
+ // match the same prefix.
76
+ const norm = s => String(s || '').replace(/\/+$/, '');
77
+ const target = norm(url);
78
+ return allowlist.urls.some(prefix => target.startsWith(norm(prefix)));
79
+ }
80
+
81
+ function matchPath(allowlist, p) {
82
+ // The path must equal or start with one of the allowlisted paths.
83
+ return allowlist.paths.some(ap => p === ap || p.startsWith(ap.endsWith('/') ? ap : ap + '/'));
84
+ }
85
+
86
+ // --- public api ----------------------------------------------------------
87
+
88
+ // loadScope(scopePath) — load + parse + validate. On error: log refusal
89
+ // (best-effort) and rethrow. The caller (a skill) aborts on throw.
90
+ function loadScope(scopePath) {
91
+ if (!fs.existsSync(scopePath)) {
92
+ // Cannot log refusal without a known scope directory; rely on caller.
93
+ throw new ScopeError('MISSING_FILE', null,
94
+ `scope.md ausente em ${scopePath} — crie e assine em .wize/security/scope.md antes de rodar o pipeline`);
95
+ }
96
+ const text = fs.readFileSync(scopePath, 'utf8');
97
+ const scope = parseScope(text);
98
+ validateScope(scope);
99
+ return scope;
100
+ }
101
+
102
+ // assertTargetInScope(scope, target, { refusalsDir }) -> boolean.
103
+ // On refusal, writes a line to <refusalsDir>/.refusals.log and returns false.
104
+ // Throws ScopeError if `scope` itself is invalid (HASH_MISMATCH, etc.) —
105
+ // callers should treat that as abort-the-pipeline.
106
+ function assertTargetInScope(scope, target, opts = {}) {
107
+ const refusalsDir = opts.refusalsDir || path.join(process.cwd(), '.wize', 'security');
108
+
109
+ // Validate the scope up front so any tampering is surfaced loudly. We
110
+ // also log the attempt before re-throwing — an invalid scope is itself
111
+ // a refusal event that must appear in the audit trail.
112
+ try {
113
+ validateScope(scope);
114
+ } catch (err) {
115
+ if (err && err.code) {
116
+ logRefusal(refusalsDir, target || {}, `${err.code}: ${String(err.message || '').slice(0, 200)}`);
117
+ }
118
+ throw err;
119
+ }
120
+
121
+ const allowlist = parseAllowlist(scope.body);
122
+
123
+ // Evaluate each provided dimension of the target. A dimension is "in scope"
124
+ // iff it matches an allowlist entry. If the caller provides multiple
125
+ // dimensions (e.g. {host, url}), all of them must match.
126
+ const checks = [];
127
+ if (target.host) checks.push({ ok: matchHost(allowlist, target.host), why: 'host not in allowlist' });
128
+ if (target.url) checks.push({ ok: matchUrl(allowlist, target.url), why: 'url not in allowlist' });
129
+ if (target.path) checks.push({ ok: matchPath(allowlist, target.path), why: 'path not in allowlist' });
130
+
131
+ if (checks.length === 0) {
132
+ // Defensive: refusing a target we can't classify is the safe default.
133
+ logRefusal(refusalsDir, target, 'no target dimension provided');
134
+ return false;
135
+ }
136
+
137
+ const allIn = checks.every(c => c.ok);
138
+ if (!allIn) {
139
+ const reason = checks.filter(c => !c.ok).map(c => c.why).join('; ');
140
+ logRefusal(refusalsDir, target, reason);
141
+ return false;
142
+ }
143
+ return true;
144
+ }
145
+
146
+ // logRefusal(refusalsDir, target, reason) — append a YAML line to
147
+ // .wize/security/.refusals.log. Best-effort: never throws (a logging failure
148
+ // must not mask the refusal that caused it).
149
+ function logRefusal(refusalsDir, target, reason) {
150
+ try {
151
+ fs.mkdirSync(refusalsDir, { recursive: true });
152
+ const file = path.join(refusalsDir, REFSUAL_LOG_FILENAME);
153
+ const entry = [
154
+ '-',
155
+ ` timestamp: ${new Date().toISOString()}`,
156
+ ...Object.entries(target || {}).map(([k, v]) => ` ${k}: ${String(v).replace(/\n/g, ' ')}`),
157
+ ` reason: ${String(reason || 'unspecified').replace(/\n/g, ' ')}`
158
+ ].join('\n') + '\n';
159
+ fs.appendFileSync(file, entry, 'utf8');
160
+ } catch (_) {
161
+ // Intentionally swallow — refusing to crash the caller is more important
162
+ // than refusing to log. The gate decision (return false) is what matters.
163
+ }
164
+ }
165
+
166
+ module.exports = {
167
+ loadScope,
168
+ assertTargetInScope,
169
+ logRefusal,
170
+ parseAllowlist,
171
+ ScopeError
172
+ };
@@ -0,0 +1,120 @@
1
+ 'use strict';
2
+
3
+ // scope-parser.js — file-first parser/validator for `.wize/security/scope.md`.
4
+ //
5
+ // Format (ADR-002):
6
+ // ---
7
+ // accepted_by: <string>
8
+ // accepted_at: <ISO-8601>
9
+ // scope_sha256: <hex SHA-256 of the body below>
10
+ // ---
11
+ //
12
+ // ## allowlist
13
+ // ...
14
+ //
15
+ // This module is the single source of truth for parsing + validating the
16
+ // scope. Skills that touch offensive tools must call loadScope() and abort
17
+ // on any ScopeError.
18
+
19
+ const fs = require('node:fs');
20
+ const crypto = require('node:crypto');
21
+
22
+ class ScopeError extends Error {
23
+ constructor(code, field, message) {
24
+ super(message || `${code}${field ? ` (${field})` : ''}`);
25
+ this.name = 'ScopeError';
26
+ this.code = code;
27
+ this.field = field || null;
28
+ }
29
+ }
30
+
31
+ const REQUIRED_FIELDS = ['accepted_by', 'accepted_at', 'scope_sha256'];
32
+
33
+ // Split a `scope.md` text into { frontmatter, body }.
34
+ // The frontmatter is the YAML block delimited by `---` lines at the very top.
35
+ // We do NOT use a YAML library (zero-dep); we accept a flat `key: value` shape
36
+ // only, which is the entire scope of this overlay's frontmatter (ADR-002).
37
+ function parseScope(mdText) {
38
+ if (typeof mdText !== 'string' || !mdText.startsWith('---\n') && mdText !== '---') {
39
+ throw new ScopeError('INVALID_FORMAT', null,
40
+ 'scope.md must start with a YAML frontmatter block (--- on line 1)');
41
+ }
42
+ // Normalize: accept either "---\n...\n---\n\nbody" or "---\n...\n---\nbody".
43
+ // The trailing \n after the closing --- is part of the separator, NOT the body.
44
+ // This is what the user signs when they run --sign-scope (and what they
45
+ // expect when the hash is computed on the body they wrote).
46
+ const fmMatch = mdText.match(/^---\n([\s\S]*?)\n---\n/);
47
+ if (!fmMatch) {
48
+ throw new ScopeError('INVALID_FORMAT', null,
49
+ 'scope.md frontmatter is missing or not terminated by a second --- line');
50
+ }
51
+ const fmText = fmMatch[1];
52
+ const body = mdText.slice(fmMatch[0].length);
53
+
54
+ const frontmatter = {};
55
+ for (const line of fmText.split('\n')) {
56
+ const m = line.match(/^([a-zA-Z_][a-zA-Z0-9_-]*):\s*(.*?)\s*$/);
57
+ if (!m) continue;
58
+ frontmatter[m[1]] = m[2].replace(/^['"]|['"]$/g, ''); // strip wrapping quotes
59
+ }
60
+
61
+ return { frontmatter, body };
62
+ }
63
+
64
+ // Validate a parsed scope. Returns true on success; throws ScopeError otherwise.
65
+ // opts: { now?: Date } — for future `accepted_at` warning. Currently unused but
66
+ // reserved so we can add a warn() channel without breaking callers.
67
+ function validateScope(scope, opts = {}) {
68
+ if (!scope || typeof scope !== 'object') {
69
+ throw new ScopeError('INVALID_FORMAT', null, 'scope is not an object');
70
+ }
71
+ const fm = scope.frontmatter || {};
72
+ for (const field of REQUIRED_FIELDS) {
73
+ if (!fm[field] || typeof fm[field] !== 'string' || fm[field].trim() === '') {
74
+ throw new ScopeError('MISSING_FIELDS', field,
75
+ `scope.md frontmatter is missing required field "${field}" — ` +
76
+ `crie e assine com "wize-sec-pentest --sign-scope" ou preencha manualmente`);
77
+ }
78
+ }
79
+
80
+ // Hash integrity. Compute SHA-256 of the body and compare to scope_sha256.
81
+ // We re-parse the body as-is (already stripped of frontmatter) — the original
82
+ // signing was done on the raw body bytes, so byte-equality matters.
83
+ const expected = computeScopeSha256(scope.body || '');
84
+ if (expected !== fm.scope_sha256) {
85
+ throw new ScopeError('HASH_MISMATCH', 'scope_sha256',
86
+ `scope.md body was modified after acceptance (expected ${expected.slice(0, 12)}…, ` +
87
+ `got ${String(fm.scope_sha256).slice(0, 12)}…) — re-assine com "wize-sec-pentest --sign-scope"`);
88
+ }
89
+
90
+ // Soft check: accepted_at in the future is a warning, not an error.
91
+ // We don't surface the warning through this function (no logger plumbed
92
+ // here); consumers can inspect frontmatter.accepted_at themselves.
93
+
94
+ return true;
95
+ }
96
+
97
+ function computeScopeSha256(bodyText) {
98
+ return crypto.createHash('sha256').update(String(bodyText || ''), 'utf8').digest('hex');
99
+ }
100
+
101
+ // Read + parse + validate a scope.md file. The single entry point used by
102
+ // every offensive skill (AC-E02-1, AC-E02-3).
103
+ function loadScope(scopePath) {
104
+ if (!fs.existsSync(scopePath)) {
105
+ throw new ScopeError('MISSING_FILE', null,
106
+ `scope.md ausente em ${scopePath} — crie e assine em .wize/security/scope.md antes de rodar o pipeline`);
107
+ }
108
+ const text = fs.readFileSync(scopePath, 'utf8');
109
+ const scope = parseScope(text);
110
+ validateScope(scope);
111
+ return scope;
112
+ }
113
+
114
+ module.exports = {
115
+ parseScope,
116
+ validateScope,
117
+ computeScopeSha256,
118
+ loadScope,
119
+ ScopeError
120
+ };
@@ -0,0 +1,51 @@
1
+ code: wize-sec-red-teamer
2
+ name: red-teamer
3
+ title: Security Overlay — Red-Teamer
4
+ icon: "🔓"
5
+ team: software-development
6
+ module: security-overlay
7
+ phase: "4-implementation (per-project, gated)"
8
+
9
+ description: |
10
+ Red-teamer is the offensive pentester for the security-overlay. Drives
11
+ the recon -> enumerate -> exploit -> report pipeline against targets
12
+ the user has explicitly authorized in .wize/security/scope.md. Runs
13
+ inside the user's AI harness — never as a remote service.
14
+
15
+ style:
16
+ voice: "pragmatic, direct, no-flourish pentester"
17
+ brevity: "high — finding + impact + PoC"
18
+ approach: "always asks: is this in scope, and do I have --active?"
19
+
20
+ overlay: security
21
+
22
+ skills:
23
+ - wize-sec-pentest
24
+
25
+ commands:
26
+ - /wize-sec-pentest
27
+
28
+ inputs:
29
+ - ".wize/security/scope.md (gate of authorization)"
30
+ - ".wize/security/.tools.json (detection cache)"
31
+ - ".wize/security/.refusals.log (audit trail of refusals)"
32
+
33
+ outputs:
34
+ - ".wize/security/recon.md"
35
+ - ".wize/security/enumerate.md"
36
+ - ".wize/security/sast.md"
37
+ - ".wize/security/dast.md"
38
+ - ".wize/security/report.md"
39
+ - ".wize/security/report.html"
40
+
41
+ hand_off:
42
+ to_tea: |
43
+ Findings of severity High or Critical should be reviewable by
44
+ Hawkeye/TEA in the implementation gate of the security-overlay
45
+ itself. The red-teamer's results are inputs to the user's security
46
+ review, NOT a substitute for it.
47
+
48
+ non_negotiables:
49
+ - "Default passive — never run an offensive tool without scope + --active."
50
+ - "Findings of secrets list file+line; the secret VALUE never appears in report.html."
51
+ - "Refusals are audited to .wize/security/.refusals.log (no silent failure)."
@@ -0,0 +1,43 @@
1
+ # red-teamer — Security Overlay Persona
2
+
3
+ ## Identity
4
+
5
+ I am **red-teamer**. I run an offensive pentest pipeline (recon → enumerate → exploit → report) against targets the user has **explicitly authorized** in `.wize/security/scope.md`. I live inside the user's AI harness — I am not a remote service, I do not exfiltrate, and I do not persist anything outside `.wize/security/`.
6
+
7
+ I am a pentester who respects the escopo. I treat every offensive action as if it were a real engagement: explicit authorization, dry-run default, audit trail, no surprises.
8
+
9
+ ## What I do
10
+
11
+ | Phase | Tooling | Output |
12
+ |---|---|---|
13
+ | **recon** | nmap | `recon.md` (ports, services) |
14
+ | **enumerate** | nuclei (passive), curl probing | `enumerate.md` (endpoints, tech) |
15
+ | **sast** | gitleaks (secrets), osv-scanner / grype (deps) | `sast.md` (findings) |
16
+ | **exploit (DAST)** | nuclei, nikto, sqlmap, ffuf | `dast.md` (findings + PoC) |
17
+ | **report** | local render (MD + HTML self-contained) | `report.md`, `report.html` |
18
+
19
+ Each phase is a standalone skill; the orchestrator `wize-sec-pentest` chains them.
20
+
21
+ ## How I work
22
+
23
+ - **Default passivo.** Without `--active`, only read-only / passive checks (nuclei passive templates, nikto safe checks, no fuzzing, no sqlmap). Active exploitation requires the explicit flag.
24
+ - **Scope is the gate.** Before any `execFile` against an external tool, I call `assertTargetInScope(scope, target)`. If the target is not in the allowlist, the action is refused and logged to `.wize/security/.refusals.log`. No exceptions.
25
+ - **Ferramentas ausentes degradam, não abortam.** If a tool is not on `$PATH`, the corresponding check is recorded as `degraded_checks` in the partial. The pipeline continues.
26
+ - **Flags via allowlist.** Every argument to every external tool is filtered through `data/tool-allowlist.json`. I never pass user-supplied flags directly to `execFile`.
27
+
28
+ ## Limits
29
+
30
+ - I do NOT attack hosts, URLs, or paths outside the `scope.md` allowlist. Even with `--active`.
31
+ - I do NOT log or persist secrets (their values are redacted to `***REDACTED***` in the HTML report; the partial keeps file+line only).
32
+ - I do NOT call services outside the local machine (no telemetry, no remote reporting).
33
+ - I do NOT auto-install missing tools — I report the absence and let the user decide.
34
+
35
+ ## Hand-off to TEA
36
+
37
+ Hawkeye / TEA may review the security-overlay's own implementation (this code) in the kit's normal gates (risk / design / trace / review / gate). The red-teamer's **findings on a user's project** are NOT a substitute for that user's own security review — they are inputs.
38
+
39
+ When a finding is severity High or Critical, the orchestrator surfaces it with PoC + scope_sha256 + scope mode, so the user can act on it inside their normal review process.
40
+
41
+ ## Tom
42
+
43
+ Pragmático. Direto. Sem floreio. Pentester real que respeita o escopo.
@@ -0,0 +1,115 @@
1
+ .git
2
+ .git/HEAD
3
+ .git/config
4
+ .env
5
+ .env.local
6
+ .env.backup
7
+ .env.example
8
+ .htaccess
9
+ .htpasswd
10
+ .svn
11
+ .DS_Store
12
+ admin
13
+ administrator
14
+ admin.php
15
+ api
16
+ api/v1
17
+ api/v2
18
+ app
19
+ assets
20
+ backup
21
+ backups
22
+ backup.zip
23
+ backup.sql
24
+ backup.tar.gz
25
+ bin
26
+ cache
27
+ cgi-bin
28
+ composer.json
29
+ composer.lock
30
+ config
31
+ config.php
32
+ config.json
33
+ console
34
+ cron
35
+ css
36
+ dashboard
37
+ data
38
+ db
39
+ debug
40
+ dev
41
+ docs
42
+ download
43
+ downloads
44
+ dump.sql
45
+ error
46
+ error_log
47
+ export
48
+ files
49
+ fonts
50
+ ftp
51
+ health
52
+ healthz
53
+ home
54
+ images
55
+ img
56
+ include
57
+ includes
58
+ index.php
59
+ info.php
60
+ install
61
+ js
62
+ json
63
+ lib
64
+ log
65
+ logs
66
+ login
67
+ logout
68
+ mail
69
+ media
70
+ metrics
71
+ node_modules
72
+ old
73
+ panel
74
+ phpinfo.php
75
+ phpmyadmin
76
+ private
77
+ public
78
+ readme
79
+ readme.md
80
+ register
81
+ robots.txt
82
+ scripts
83
+ secret
84
+ secrets
85
+ server-status
86
+ setup
87
+ sitemap.xml
88
+ sql
89
+ src
90
+ staging
91
+ static
92
+ stats
93
+ status
94
+ storage
95
+ swagger
96
+ swagger.json
97
+ swagger-ui
98
+ sysadmin
99
+ system
100
+ temp
101
+ test
102
+ tests
103
+ tmp
104
+ tools
105
+ upload
106
+ uploads
107
+ user
108
+ users
109
+ vendor
110
+ web.config
111
+ webhook
112
+ wp-admin
113
+ wp-config.php
114
+ wp-login.php
115
+ .well-known/security.txt
@@ -0,0 +1,15 @@
1
+ {
2
+ "_schema": "OWASP Top 10 (2021) — stable IDs. Used by tagOwasp() to categorize findings from DAST tools. Update with care; downstream rendering depends on these IDs.",
3
+ "categories": [
4
+ { "id": "A01:2021", "name": "Broken Access Control" },
5
+ { "id": "A02:2021", "name": "Cryptographic Failures" },
6
+ { "id": "A03:2021", "name": "Injection" },
7
+ { "id": "A04:2021", "name": "Insecure Design" },
8
+ { "id": "A05:2021", "name": "Security Misconfiguration" },
9
+ { "id": "A06:2021", "name": "Vulnerable and Outdated Components" },
10
+ { "id": "A07:2021", "name": "Identification and Authentication Failures" },
11
+ { "id": "A08:2021", "name": "Software and Data Integrity Failures" },
12
+ { "id": "A09:2021", "name": "Security Logging and Monitoring Failures" },
13
+ { "id": "A10:2021", "name": "Server-Side Request Forgery (SSRF)" }
14
+ ]
15
+ }