@clear-capabilities/agentic-security-scanner 0.74.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/CHANGELOG.md +1580 -0
- package/bin/.agentic-security/findings.json +1577 -0
- package/bin/.agentic-security/last-scan.json +1577 -0
- package/bin/.agentic-security/last-scan.json.sig +1 -0
- package/bin/.agentic-security/scan-history.json +465 -0
- package/bin/.agentic-security/streak.json +25 -0
- package/bin/agentic-security-audit.js +198 -0
- package/bin/agentic-security-consistency.js +80 -0
- package/bin/agentic-security-diff.js +136 -0
- package/bin/agentic-security-lsp.js +12 -0
- package/bin/agentic-security-mcp.js +40 -0
- package/bin/agentic-security-rule.js +153 -0
- package/bin/agentic-security.js +1683 -0
- package/dist/117.index.js +207 -0
- package/dist/178.index.js +250 -0
- package/dist/218.index.js +793 -0
- package/dist/227.index.js +192 -0
- package/dist/301.index.js +167 -0
- package/dist/384.index.js +18 -0
- package/dist/476.index.js +126 -0
- package/dist/513.index.js +373 -0
- package/dist/520.index.js +13 -0
- package/dist/601.index.js +1038 -0
- package/dist/634.index.js +1892 -0
- package/dist/637.index.js +216 -0
- package/dist/660.index.js +131 -0
- package/dist/675.index.js +451 -0
- package/dist/826.index.js +188 -0
- package/dist/830.index.js +133 -0
- package/dist/agentic-security.mjs +272 -0
- package/dist/agentic-security.mjs.sha256 +1 -0
- package/dist/calibration-seed.json +27 -0
- package/package.json +77 -0
- package/src/.agentic-security/findings.json +80844 -0
- package/src/.agentic-security/last-scan.json +80844 -0
- package/src/.agentic-security/last-scan.json.sig +1 -0
- package/src/.agentic-security/scan-history.json +8408 -0
- package/src/.agentic-security/streak.json +26 -0
- package/src/badge.js +188 -0
- package/src/compare.js +203 -0
- package/src/dataflow/.agentic-security/findings.json +3487 -0
- package/src/dataflow/.agentic-security/last-scan.json +3487 -0
- package/src/dataflow/.agentic-security/last-scan.json.sig +1 -0
- package/src/dataflow/.agentic-security/scan-history.json +735 -0
- package/src/dataflow/.agentic-security/streak.json +24 -0
- package/src/dataflow/CLAUDE.md +38 -0
- package/src/dataflow/access-paths.js +172 -0
- package/src/dataflow/async-sequencing.js +177 -0
- package/src/dataflow/backward.js +201 -0
- package/src/dataflow/catalog-expanded.js +485 -0
- package/src/dataflow/catalog.js +659 -0
- package/src/dataflow/cross-repo.js +219 -0
- package/src/dataflow/engine.js +588 -0
- package/src/dataflow/exception-flow.js +116 -0
- package/src/dataflow/exploit-prover.js +187 -0
- package/src/dataflow/higher-order.js +221 -0
- package/src/dataflow/ifds.js +347 -0
- package/src/dataflow/implicit-flow.js +129 -0
- package/src/dataflow/incremental.js +229 -0
- package/src/dataflow/index.js +181 -0
- package/src/dataflow/numeric-domain.js +192 -0
- package/src/dataflow/path-feasibility.js +114 -0
- package/src/dataflow/points-to.js +337 -0
- package/src/dataflow/polyglot.js +190 -0
- package/src/dataflow/proven-clean.js +159 -0
- package/src/dataflow/receiver-context.js +76 -0
- package/src/dataflow/sanitizer-proof.js +154 -0
- package/src/dataflow/soft-taint.js +140 -0
- package/src/dataflow/string-domain.js +234 -0
- package/src/dataflow/stub-aware-filter.js +100 -0
- package/src/dataflow/summaries.js +132 -0
- package/src/dataflow/symbolic-exec.js +238 -0
- package/src/dataflow/tabulation.js +135 -0
- package/src/engine.js +7763 -0
- package/src/history-scan.js +229 -0
- package/src/index.js +3 -0
- package/src/integrations/.agentic-security/findings.json +1504 -0
- package/src/integrations/.agentic-security/last-scan.json +1504 -0
- package/src/integrations/.agentic-security/scan-history.json +40 -0
- package/src/integrations/.agentic-security/streak.json +21 -0
- package/src/integrations/index.js +321 -0
- package/src/integrations/tickets.js +200 -0
- package/src/ir/.agentic-security/findings.json +3036 -0
- package/src/ir/.agentic-security/last-scan.json +3036 -0
- package/src/ir/.agentic-security/last-scan.json.sig +1 -0
- package/src/ir/.agentic-security/scan-history.json +364 -0
- package/src/ir/.agentic-security/streak.json +23 -0
- package/src/ir/CLAUDE.md +172 -0
- package/src/ir/callgraph.js +73 -0
- package/src/ir/class-hierarchy.js +195 -0
- package/src/ir/index.js +152 -0
- package/src/ir/parser-cs.js +260 -0
- package/src/ir/parser-java.js +286 -0
- package/src/ir/parser-js.js +413 -0
- package/src/ir/parser-kt.js +258 -0
- package/src/ir/parser-py-cst.js +136 -0
- package/src/ir/parser-py.helper.py +501 -0
- package/src/ir/parser-py.js +312 -0
- package/src/ir/ssa.js +315 -0
- package/src/ir/type-stubs.js +288 -0
- package/src/leaderboard.js +152 -0
- package/src/llm-validator/.agentic-security/findings.json +1891 -0
- package/src/llm-validator/.agentic-security/last-scan.json +1891 -0
- package/src/llm-validator/.agentic-security/last-scan.json.sig +1 -0
- package/src/llm-validator/.agentic-security/scan-history.json +168 -0
- package/src/llm-validator/.agentic-security/streak.json +20 -0
- package/src/llm-validator/consistency.js +141 -0
- package/src/llm-validator/index.js +437 -0
- package/src/lsp/.agentic-security/findings.json +28 -0
- package/src/lsp/.agentic-security/last-scan.json +28 -0
- package/src/lsp/.agentic-security/scan-history.json +79 -0
- package/src/lsp/.agentic-security/streak.json +22 -0
- package/src/lsp/server.js +275 -0
- package/src/mcp/.agentic-security/findings.json +8358 -0
- package/src/mcp/.agentic-security/last-scan.json +8358 -0
- package/src/mcp/.agentic-security/last-scan.json.sig +1 -0
- package/src/mcp/.agentic-security/scan-history.json +1125 -0
- package/src/mcp/.agentic-security/streak.json +22 -0
- package/src/mcp/CLAUDE.md +54 -0
- package/src/mcp/audit.js +136 -0
- package/src/mcp/redact.js +75 -0
- package/src/mcp/server.js +158 -0
- package/src/mcp/stdio.js +83 -0
- package/src/mcp/tools.js +940 -0
- package/src/mcp/validate.js +49 -0
- package/src/personality.js +164 -0
- package/src/poc-video.js +239 -0
- package/src/posture/.agentic-security/findings.json +51239 -0
- package/src/posture/.agentic-security/last-scan.json +51239 -0
- package/src/posture/.agentic-security/last-scan.json.sig +1 -0
- package/src/posture/.agentic-security/scan-history.json +5557 -0
- package/src/posture/.agentic-security/streak.json +24 -0
- package/src/posture/CLAUDE.md +42 -0
- package/src/posture/adversarial-self-test.js +114 -0
- package/src/posture/adversary-agent.js +204 -0
- package/src/posture/agents-memory.js +135 -0
- package/src/posture/ai-code-fingerprint.js +171 -0
- package/src/posture/aibom.js +284 -0
- package/src/posture/api-inventory.js +96 -0
- package/src/posture/attack-playbooks.js +305 -0
- package/src/posture/auditor-agent.js +115 -0
- package/src/posture/auth-posture-import.js +135 -0
- package/src/posture/baseline-compare.js +114 -0
- package/src/posture/blast-radius.js +836 -0
- package/src/posture/bounty-prediction.js +141 -0
- package/src/posture/business-logic.js +239 -0
- package/src/posture/calibration-drift.js +93 -0
- package/src/posture/calibration-seed.json +27 -0
- package/src/posture/calibration.js +204 -0
- package/src/posture/clustering.js +75 -0
- package/src/posture/concurrency-checker.js +265 -0
- package/src/posture/confidence.js +65 -0
- package/src/posture/container-runtime.js +149 -0
- package/src/posture/counterfactual.js +109 -0
- package/src/posture/cross-lang-graphql.js +165 -0
- package/src/posture/cross-lang-grpc.js +166 -0
- package/src/posture/cross-lang-meta.js +101 -0
- package/src/posture/cross-lang-openapi.js +187 -0
- package/src/posture/cross-lang-orm.js +153 -0
- package/src/posture/cross-lang-queues.js +210 -0
- package/src/posture/crown-jewels.js +110 -0
- package/src/posture/custom-rules.js +361 -0
- package/src/posture/cve-alert-daemon.js +433 -0
- package/src/posture/cve-lookup.js +129 -0
- package/src/posture/dead-code.js +430 -0
- package/src/posture/defender-agent.js +158 -0
- package/src/posture/deploy-platform.js +204 -0
- package/src/posture/detector-fuzz.js +61 -0
- package/src/posture/deterministic.js +99 -0
- package/src/posture/drift.js +165 -0
- package/src/posture/epss.js +156 -0
- package/src/posture/exploitability-probability.js +212 -0
- package/src/posture/exploitability.js +121 -0
- package/src/posture/feature-flags.js +110 -0
- package/src/posture/finding-defaults.js +132 -0
- package/src/posture/fix-history.js +411 -0
- package/src/posture/fix-plan.js +121 -0
- package/src/posture/fix-verify-loop.js +157 -0
- package/src/posture/fix-verify.js +130 -0
- package/src/posture/flow-narration.js +105 -0
- package/src/posture/grader-calibration.js +156 -0
- package/src/posture/harness-discovery.js +113 -0
- package/src/posture/holdout-eval.js +144 -0
- package/src/posture/iac-reachability.js +163 -0
- package/src/posture/iam-policy.js +128 -0
- package/src/posture/integrity.js +97 -0
- package/src/posture/learning.js +166 -0
- package/src/posture/license-policy.js +109 -0
- package/src/posture/llm-redteam-prompts.js +418 -0
- package/src/posture/llm-redteam.js +303 -0
- package/src/posture/material-change.js +163 -0
- package/src/posture/mitigation-composite.js +55 -0
- package/src/posture/mttr.js +91 -0
- package/src/posture/network-policy-import.js +126 -0
- package/src/posture/path-predicates.js +99 -0
- package/src/posture/persona-prioritization.js +153 -0
- package/src/posture/poc-cwe-map.js +51 -0
- package/src/posture/poc-generator.js +500 -0
- package/src/posture/policy-gate.js +174 -0
- package/src/posture/pre-incident-archaeology.js +110 -0
- package/src/posture/profile.js +93 -0
- package/src/posture/reachability-filter.js +42 -0
- package/src/posture/regression-test-gen.js +200 -0
- package/src/posture/reverse-blast-radius.js +110 -0
- package/src/posture/router.js +109 -0
- package/src/posture/rule-overrides.js +198 -0
- package/src/posture/rule-pack-signing.js +209 -0
- package/src/posture/rule-packs.js +143 -0
- package/src/posture/rule-synthesis.js +108 -0
- package/src/posture/ruleset-version.js +71 -0
- package/src/posture/sbom.js +129 -0
- package/src/posture/schema-aware-bridge.js +207 -0
- package/src/posture/security-trend.js +87 -0
- package/src/posture/semantic-clone.js +114 -0
- package/src/posture/specification-mining.js +170 -0
- package/src/posture/stable-id.js +75 -0
- package/src/posture/stack-playbook.js +229 -0
- package/src/posture/streak.js +249 -0
- package/src/posture/suppressions.js +135 -0
- package/src/posture/telemetry-ingest.js +112 -0
- package/src/posture/threat-model.js +145 -0
- package/src/posture/three-agent-pipeline.js +74 -0
- package/src/posture/triage.js +146 -0
- package/src/posture/trust-boundary-diagram.js +115 -0
- package/src/posture/type-narrowing.js +129 -0
- package/src/posture/validator-metrics.js +179 -0
- package/src/posture/verifier-ephemeral.js +118 -0
- package/src/posture/verifier-target.js +147 -0
- package/src/posture/verifier.js +257 -0
- package/src/posture/version.js +75 -0
- package/src/posture/waf-ingest.js +200 -0
- package/src/posture/why-fired.js +141 -0
- package/src/pr-comment.js +172 -0
- package/src/pr-delta.js +198 -0
- package/src/report/.agentic-security/findings.json +79 -0
- package/src/report/.agentic-security/last-scan.json +79 -0
- package/src/report/.agentic-security/last-scan.json.sig +1 -0
- package/src/report/.agentic-security/scan-history.json +332 -0
- package/src/report/.agentic-security/streak.json +23 -0
- package/src/report/index.js +1136 -0
- package/src/report/mascot.js +42 -0
- package/src/runScan.js +141 -0
- package/src/sast/.agentic-security/findings.json +5051 -0
- package/src/sast/.agentic-security/last-scan.json +5051 -0
- package/src/sast/.agentic-security/last-scan.json.sig +1 -0
- package/src/sast/.agentic-security/scan-history.json +788 -0
- package/src/sast/.agentic-security/streak.json +23 -0
- package/src/sast/CLAUDE.md +39 -0
- package/src/sast/_comment-strip.js +46 -0
- package/src/sast/agent-tool-escalation.js +131 -0
- package/src/sast/auth-provider.js +171 -0
- package/src/sast/authz.js +236 -0
- package/src/sast/bench-shape/.agentic-security/findings.json +28 -0
- package/src/sast/bench-shape/.agentic-security/last-scan.json +28 -0
- package/src/sast/bench-shape/.agentic-security/scan-history.json +24 -0
- package/src/sast/bench-shape/.agentic-security/streak.json +22 -0
- package/src/sast/bench-shape/index.js +62 -0
- package/src/sast/claude-hook-injection.js +199 -0
- package/src/sast/claude-md-prompt-injection.js +170 -0
- package/src/sast/claude-settings.js +165 -0
- package/src/sast/client-side.js +149 -0
- package/src/sast/cpp-bench-extras.js +122 -0
- package/src/sast/cpp-dataflow.js +430 -0
- package/src/sast/cpp.js +248 -0
- package/src/sast/csharp.js +152 -0
- package/src/sast/csrf.js +82 -0
- package/src/sast/dart-flutter.js +173 -0
- package/src/sast/db-rls.js +147 -0
- package/src/sast/db-taint.js +215 -0
- package/src/sast/defi-deep.js +242 -0
- package/src/sast/deserialization-gadgets.js +113 -0
- package/src/sast/django-hardening.js +230 -0
- package/src/sast/env-hygiene.js +125 -0
- package/src/sast/fastapi-hardening.js +145 -0
- package/src/sast/go-extended.js +84 -0
- package/src/sast/host-header.js +106 -0
- package/src/sast/index.js +17 -0
- package/src/sast/java-ast-folding.js +561 -0
- package/src/sast/java-bench-extras.js +708 -0
- package/src/sast/java-collection-passthrough.js +178 -0
- package/src/sast/java-constant-fold.js +244 -0
- package/src/sast/java-deserialization.js +125 -0
- package/src/sast/jndi.js +104 -0
- package/src/sast/juliet-shape.js +324 -0
- package/src/sast/jwt-exp.js +104 -0
- package/src/sast/kotlin.js +82 -0
- package/src/sast/laravel-hardening.js +198 -0
- package/src/sast/ldap-injection.js +100 -0
- package/src/sast/llm-owasp.js +465 -0
- package/src/sast/llm-stored-prompt.js +103 -0
- package/src/sast/llm-trading-agent.js +161 -0
- package/src/sast/llm.js +308 -0
- package/src/sast/logic.js +140 -0
- package/src/sast/mass-assignment.js +101 -0
- package/src/sast/mcp-audit.js +242 -0
- package/src/sast/mobile-manifest.js +195 -0
- package/src/sast/model-load.js +164 -0
- package/src/sast/mutation-xss.js +87 -0
- package/src/sast/nosql-injection.js +82 -0
- package/src/sast/open-redirect.js +119 -0
- package/src/sast/php.js +91 -0
- package/src/sast/pipeline.js +122 -0
- package/src/sast/primary-cwe-java.js +155 -0
- package/src/sast/prompt-firewall.js +151 -0
- package/src/sast/prompt-template.js +157 -0
- package/src/sast/prototype-pollution.js +112 -0
- package/src/sast/python-sinks.js +195 -0
- package/src/sast/quarkus-hardening.js +102 -0
- package/src/sast/rag-poisoning.js +118 -0
- package/src/sast/rate-limit.js +128 -0
- package/src/sast/response-splitting.js +138 -0
- package/src/sast/ruby.js +108 -0
- package/src/sast/rust.js +105 -0
- package/src/sast/solidity.js +167 -0
- package/src/sast/springboot-hardening.js +186 -0
- package/src/sast/ssrf-cloud-metadata.js +80 -0
- package/src/sast/ssti.js +116 -0
- package/src/sast/swift.js +162 -0
- package/src/sast/toctou.js +95 -0
- package/src/sast/webhook.js +101 -0
- package/src/sast/xpath-injection.js +51 -0
- package/src/sast/xxe.js +140 -0
- package/src/sast/zip-slip.js +200 -0
- package/src/sca/base-images.json +45 -0
- package/src/sca/container.js +107 -0
- package/src/sca/dep-confusion.js +134 -0
- package/src/sca/index.js +6 -0
- package/src/sca/popular-packages.json +41 -0
- package/src/sca/sarif-ingest.js +187 -0
- package/src/sca/vuln-function-hints.json +89 -0
- package/src/secrets/index.js +4 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Minimal JSON Schema validator — just the subset our tool schemas use.
|
|
2
|
+
// No deps. Throws on invalid input with a path-prefixed error message.
|
|
3
|
+
//
|
|
4
|
+
// Supported keywords: type (object/array/string/boolean/number),
|
|
5
|
+
// required, properties, items, enum, minItems, maxItems, maxLength,
|
|
6
|
+
// minLength, additionalProperties (only as `false` — strict).
|
|
7
|
+
|
|
8
|
+
const TYPE_OF = (v) => {
|
|
9
|
+
if (v === null) return 'null';
|
|
10
|
+
if (Array.isArray(v)) return 'array';
|
|
11
|
+
return typeof v;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function validate(schema, value, path = 'arguments') {
|
|
15
|
+
if (!schema) return;
|
|
16
|
+
const t = schema.type;
|
|
17
|
+
if (t === 'object') {
|
|
18
|
+
if (TYPE_OF(value) !== 'object') throw new Error(`${path}: expected object, got ${TYPE_OF(value)}`);
|
|
19
|
+
for (const req of schema.required || []) {
|
|
20
|
+
if (!(req in value)) throw new Error(`${path}: missing required property "${req}"`);
|
|
21
|
+
}
|
|
22
|
+
if (schema.additionalProperties === false) {
|
|
23
|
+
const allowed = new Set(Object.keys(schema.properties || {}));
|
|
24
|
+
for (const k of Object.keys(value)) {
|
|
25
|
+
if (!allowed.has(k)) throw new Error(`${path}: unexpected property "${k}"`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
for (const [k, sub] of Object.entries(schema.properties || {})) {
|
|
29
|
+
if (k in value) validate(sub, value[k], `${path}.${k}`);
|
|
30
|
+
}
|
|
31
|
+
} else if (t === 'array') {
|
|
32
|
+
if (!Array.isArray(value)) throw new Error(`${path}: expected array, got ${TYPE_OF(value)}`);
|
|
33
|
+
if (schema.minItems != null && value.length < schema.minItems) throw new Error(`${path}: minItems=${schema.minItems}, got length=${value.length}`);
|
|
34
|
+
if (schema.maxItems != null && value.length > schema.maxItems) throw new Error(`${path}: maxItems=${schema.maxItems}, got length=${value.length}`);
|
|
35
|
+
if (schema.items) for (let i = 0; i < value.length; i++) validate(schema.items, value[i], `${path}[${i}]`);
|
|
36
|
+
} else if (t === 'string') {
|
|
37
|
+
if (typeof value !== 'string') throw new Error(`${path}: expected string, got ${TYPE_OF(value)}`);
|
|
38
|
+
if (schema.enum && !schema.enum.includes(value)) throw new Error(`${path}: must be one of [${schema.enum.join(', ')}]`);
|
|
39
|
+
if (schema.maxLength != null && value.length > schema.maxLength) throw new Error(`${path}: maxLength=${schema.maxLength}, got length=${value.length}`);
|
|
40
|
+
if (schema.minLength != null && value.length < schema.minLength) throw new Error(`${path}: minLength=${schema.minLength}, got length=${value.length}`);
|
|
41
|
+
} else if (t === 'boolean') {
|
|
42
|
+
if (typeof value !== 'boolean') throw new Error(`${path}: expected boolean, got ${TYPE_OF(value)}`);
|
|
43
|
+
} else if (t === 'number' || t === 'integer') {
|
|
44
|
+
if (typeof value !== 'number') throw new Error(`${path}: expected number, got ${TYPE_OF(value)}`);
|
|
45
|
+
if (t === 'integer' && !Number.isInteger(value)) throw new Error(`${path}: expected integer`);
|
|
46
|
+
if (schema.minimum != null && value < schema.minimum) throw new Error(`${path}: < minimum (${schema.minimum})`);
|
|
47
|
+
if (schema.maximum != null && value > schema.maximum) throw new Error(`${path}: > maximum (${schema.maximum})`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
// Security personality voices (v0.74).
|
|
2
|
+
//
|
|
3
|
+
// Same findings, dramatically different shareability. Three modes:
|
|
4
|
+
//
|
|
5
|
+
// - sage (default): calm explainer. "Consider that..." Reasonable
|
|
6
|
+
// tone for compliance audiences + general engineering.
|
|
7
|
+
// - cassandra: alarmist. "This WILL ship as a CVE." Sells urgency to
|
|
8
|
+
// management; useful for getting buy-in on remediation
|
|
9
|
+
// budget.
|
|
10
|
+
// - vince: drill-sergeant. "Ship this and you're paged at 3am."
|
|
11
|
+
// Sells discipline; popular with vets / staff engineers
|
|
12
|
+
// who want zero ceremony.
|
|
13
|
+
//
|
|
14
|
+
// Selection: AGENTIC_SECURITY_PERSONALITY env var, or `personality:`
|
|
15
|
+
// field on the renderer call. Defaults to 'sage'.
|
|
16
|
+
//
|
|
17
|
+
// What's actually different per voice:
|
|
18
|
+
// 1. The OPENING line of any rendered report
|
|
19
|
+
// 2. The PHRASING of severity descriptors
|
|
20
|
+
// 3. The CLOSING line / call-to-action
|
|
21
|
+
// 4. Specific lexical choices (emoji density, hedging vs. assertive,
|
|
22
|
+
// ALL CAPS for emphasis)
|
|
23
|
+
//
|
|
24
|
+
// What's THE SAME across voices:
|
|
25
|
+
// - The technical content (CWEs, finding IDs, file:line, remediation)
|
|
26
|
+
// - Severity assignments (a critical is a critical regardless of voice)
|
|
27
|
+
// - Counts + summary statistics
|
|
28
|
+
// - The decision (blocking-merge or non-blocking)
|
|
29
|
+
//
|
|
30
|
+
// Operators changing voice does NOT change WHAT findings fire — only
|
|
31
|
+
// how they read.
|
|
32
|
+
|
|
33
|
+
const VOICES = {
|
|
34
|
+
sage: {
|
|
35
|
+
glyph: '🛡',
|
|
36
|
+
openingClean: (n) => n === 0
|
|
37
|
+
? `No security delta. Safe to merge.`
|
|
38
|
+
: `Clean scan — ${n} pre-existing findings unchanged.`,
|
|
39
|
+
openingNeedsWork: (n, files) =>
|
|
40
|
+
`I looked at the ${files} file${files === 1 ? '' : 's'} you changed and noticed ${n} new finding${n === 1 ? '' : 's'} worth your attention.`,
|
|
41
|
+
severityWord: (s) => ({
|
|
42
|
+
critical: 'critical',
|
|
43
|
+
high: 'high-severity',
|
|
44
|
+
medium: 'medium-severity',
|
|
45
|
+
low: 'low-impact',
|
|
46
|
+
info: 'informational',
|
|
47
|
+
}[s] || s),
|
|
48
|
+
closingBlocking: (counts) =>
|
|
49
|
+
`**Blocking merge:** ${counts.critical} critical + ${counts.high} high. Address these before merging — happy to walk through any of them.`,
|
|
50
|
+
closingNonblocking: () =>
|
|
51
|
+
`Non-blocking, but worth fixing while context is fresh.`,
|
|
52
|
+
fixCue: 'Suggested fix',
|
|
53
|
+
},
|
|
54
|
+
cassandra: {
|
|
55
|
+
glyph: '🚨',
|
|
56
|
+
openingClean: (n) => n === 0
|
|
57
|
+
? `No new vulnerabilities introduced. The existing surface is still attackable, but you didn't make it worse.`
|
|
58
|
+
: `Scan is "clean" — but you're sitting on ${n} pre-existing findings. Today is the day they get exploited.`,
|
|
59
|
+
openingNeedsWork: (n, files) =>
|
|
60
|
+
`**${n} new attack surface${n === 1 ? '' : 's'}** opened in ${files} file${files === 1 ? '' : 's'}. This is how breaches start.`,
|
|
61
|
+
severityWord: (s) => ({
|
|
62
|
+
critical: 'CRITICAL — RCE-class',
|
|
63
|
+
high: 'high — exploitable today',
|
|
64
|
+
medium: 'medium — exploitable with chaining',
|
|
65
|
+
low: 'low — exploitable in 18 months when the toolchain catches up',
|
|
66
|
+
info: 'informational — write it down anyway',
|
|
67
|
+
}[s] || s),
|
|
68
|
+
closingBlocking: (counts) =>
|
|
69
|
+
`🚨 **DO NOT MERGE.** ${counts.critical} CRITICAL + ${counts.high} HIGH severity. Each one is a CVE waiting for a researcher. Fix or revert.`,
|
|
70
|
+
closingNonblocking: () =>
|
|
71
|
+
`No criticals — yet. Fix the rest before they chain into something that matters.`,
|
|
72
|
+
fixCue: 'Mitigation (apply now)',
|
|
73
|
+
},
|
|
74
|
+
vince: {
|
|
75
|
+
glyph: '🪖',
|
|
76
|
+
openingClean: (n) => n === 0
|
|
77
|
+
? `Clean. Move out.`
|
|
78
|
+
: `Nothing new added. ${n} legacy findings still on the board — that's tomorrow's problem.`,
|
|
79
|
+
openingNeedsWork: (n, files) =>
|
|
80
|
+
`${n} new finding${n === 1 ? '' : 's'} in ${files} file${files === 1 ? '' : 's'}. Drop and give me twenty fixes.`,
|
|
81
|
+
severityWord: (s) => ({
|
|
82
|
+
critical: 'CRITICAL',
|
|
83
|
+
high: 'HIGH',
|
|
84
|
+
medium: 'MEDIUM',
|
|
85
|
+
low: 'LOW',
|
|
86
|
+
info: 'INFO',
|
|
87
|
+
}[s] || s.toUpperCase()),
|
|
88
|
+
closingBlocking: (counts) =>
|
|
89
|
+
`**HALT.** ${counts.critical} critical, ${counts.high} high. Ship this and you're paged at 3am. Fix it before the standup.`,
|
|
90
|
+
closingNonblocking: () =>
|
|
91
|
+
`No critical or high. You're cleared to merge — but address the rest this sprint. No exceptions, no carryover.`,
|
|
92
|
+
fixCue: 'Fix',
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export function getPersonality(name) {
|
|
97
|
+
const key = String(name || process.env.AGENTIC_SECURITY_PERSONALITY || 'sage').toLowerCase();
|
|
98
|
+
return VOICES[key] || VOICES.sage;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function listPersonalities() { return Object.keys(VOICES); }
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Wrap a renderer output's opening / closing lines with the active
|
|
105
|
+
* personality. Caller passes the raw delta + the personality name (or
|
|
106
|
+
* uses env default).
|
|
107
|
+
*
|
|
108
|
+
* This is intentionally additive — the renderer's middle body (CWE
|
|
109
|
+
* 'why' paragraphs, per-finding lines) is identical across voices.
|
|
110
|
+
* Only the framing changes.
|
|
111
|
+
*/
|
|
112
|
+
export function applyPersonality(rendered, delta, personality) {
|
|
113
|
+
const voice = getPersonality(personality);
|
|
114
|
+
if (!rendered || !delta) return rendered;
|
|
115
|
+
const intro = (delta.introduced || []).length;
|
|
116
|
+
const resolved = (delta.resolved || []).length;
|
|
117
|
+
const shifted = (delta.shifted || []).length;
|
|
118
|
+
const filesN = (delta.changedFiles || []).length;
|
|
119
|
+
// Replace the existing first-paragraph greeting with the voice-specific one.
|
|
120
|
+
let opening;
|
|
121
|
+
if (intro === 0 && resolved === 0 && shifted === 0) {
|
|
122
|
+
opening = voice.openingClean(delta.head?.summary?.total || 0);
|
|
123
|
+
} else if (intro === 0) {
|
|
124
|
+
opening = voice.openingClean(delta.head?.summary?.total || 0);
|
|
125
|
+
} else {
|
|
126
|
+
opening = voice.openingNeedsWork(intro, filesN);
|
|
127
|
+
}
|
|
128
|
+
// Replace closing/footer based on blocking status.
|
|
129
|
+
const i = delta.summary?.introduced || {};
|
|
130
|
+
const blocking = (i.critical || 0) + (i.high || 0) > 0;
|
|
131
|
+
const closing = blocking
|
|
132
|
+
? voice.closingBlocking({ critical: i.critical || 0, high: i.high || 0 })
|
|
133
|
+
: voice.closingNonblocking();
|
|
134
|
+
// The rendered output has a structure: heading line, blank, opening,
|
|
135
|
+
// ..., separator line ('---'), blocking-or-nonblocking footer.
|
|
136
|
+
const lines = rendered.split('\n');
|
|
137
|
+
// Find the first non-empty content line after the heading.
|
|
138
|
+
let openingLineIdx = -1;
|
|
139
|
+
for (let i = 0; i < lines.length; i++) {
|
|
140
|
+
if (i > 0 && lines[i].trim() && !lines[i].startsWith('###')) {
|
|
141
|
+
openingLineIdx = i;
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (openingLineIdx > 0) lines[openingLineIdx] = opening;
|
|
146
|
+
// Replace the heading glyph.
|
|
147
|
+
if (lines[0]) {
|
|
148
|
+
lines[0] = lines[0].replace(/^###\s+🛡/, `### ${voice.glyph}`);
|
|
149
|
+
}
|
|
150
|
+
// Replace the closing line (last non-empty line after the '---' separator).
|
|
151
|
+
let separatorIdx = -1;
|
|
152
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
153
|
+
if (lines[i].trim() === '---') { separatorIdx = i; break; }
|
|
154
|
+
}
|
|
155
|
+
if (separatorIdx > 0) {
|
|
156
|
+
// Find the first non-empty line after the separator and replace it.
|
|
157
|
+
for (let i = separatorIdx + 1; i < lines.length; i++) {
|
|
158
|
+
if (lines[i].trim()) { lines[i] = closing; break; }
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return lines.join('\n');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export const _internal = { VOICES };
|
package/src/poc-video.js
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
// Auto-recorded PoC video / exploit-runner generator (v0.74).
|
|
2
|
+
//
|
|
3
|
+
// For each finding with an `_exploitInput` (set by the v0.71 symbolic
|
|
4
|
+
// exploit prover), generate a self-contained script the user can run
|
|
5
|
+
// against their staging/dev environment to:
|
|
6
|
+
//
|
|
7
|
+
// 1. Drive the exploit live (HTTP request, browser interaction, etc.)
|
|
8
|
+
// 2. Capture the response showing the vuln triggered
|
|
9
|
+
// 3. Record the session as a video (Playwright) or transcript (curl)
|
|
10
|
+
//
|
|
11
|
+
// Three output formats:
|
|
12
|
+
// - 'playwright' — TypeScript test that launches Chromium, records
|
|
13
|
+
// video to a known path. Best for screenshareable evidence on
|
|
14
|
+
// UI-driven exploits (DOM XSS, open redirect, CSRF, prototype
|
|
15
|
+
// pollution).
|
|
16
|
+
// - 'curl' — bash script using curl with verbose tracing.
|
|
17
|
+
// Best for backend exploits (SQLi, command injection, SSRF, path
|
|
18
|
+
// traversal, deserialization).
|
|
19
|
+
// - 'http' — RFC 7230-style raw HTTP request + expected
|
|
20
|
+
// response shape. Format-agnostic; pastable into any HTTP client.
|
|
21
|
+
//
|
|
22
|
+
// The generator does NOT execute anything — it produces a script the
|
|
23
|
+
// operator runs against their OWN environment. We never bundle a
|
|
24
|
+
// browser, never make network calls during generation.
|
|
25
|
+
|
|
26
|
+
const TEMPLATES = {
|
|
27
|
+
playwright: _playwrightTemplate,
|
|
28
|
+
curl: _curlTemplate,
|
|
29
|
+
http: _httpTemplate,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Pick the best default format for a given vuln family. UI-driven
|
|
34
|
+
* exploits get playwright; backend exploits get curl.
|
|
35
|
+
*/
|
|
36
|
+
function _defaultFormatFor(cwe) {
|
|
37
|
+
const ui = new Set(['CWE-79', 'CWE-601', 'CWE-352', 'CWE-1321']);
|
|
38
|
+
return ui.has(cwe) ? 'playwright' : 'curl';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Top-level entry. Returns a script string + a short filename hint.
|
|
43
|
+
*
|
|
44
|
+
* finding: { cwe, vuln, file, line, _exploitInput, sink, ... }
|
|
45
|
+
* opts: {
|
|
46
|
+
* format: 'playwright' | 'curl' | 'http' (default: by CWE)
|
|
47
|
+
* baseUrl: 'https://staging.example.com' (default: http://localhost:3000)
|
|
48
|
+
* route: '/api/admin/users' (default: derived from finding)
|
|
49
|
+
* method: 'POST' (default: derived from finding)
|
|
50
|
+
* }
|
|
51
|
+
*/
|
|
52
|
+
export function generatePocScript(finding, opts = {}) {
|
|
53
|
+
if (!finding) return { script: '', filename: 'poc.txt', format: null };
|
|
54
|
+
const format = opts.format || _defaultFormatFor(finding.cwe);
|
|
55
|
+
const baseUrl = opts.baseUrl || 'http://localhost:3000';
|
|
56
|
+
const route = opts.route || _inferRoute(finding) || '/api/endpoint';
|
|
57
|
+
const method = opts.method || _inferMethod(finding) || 'POST';
|
|
58
|
+
const payload = finding._exploitInput || _fallbackPayload(finding.cwe);
|
|
59
|
+
const template = TEMPLATES[format];
|
|
60
|
+
if (!template) throw new Error(`Unknown format: ${format}`);
|
|
61
|
+
const script = template({ finding, baseUrl, route, method, payload });
|
|
62
|
+
const filename = _filenameFor(finding, format);
|
|
63
|
+
return { script, filename, format, payload };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function _filenameFor(finding, format) {
|
|
67
|
+
const ext = format === 'playwright' ? 'spec.ts'
|
|
68
|
+
: format === 'curl' ? 'sh'
|
|
69
|
+
: 'http';
|
|
70
|
+
const slug = (finding.cwe || 'vuln').toLowerCase().replace(/[^a-z0-9]/g, '-');
|
|
71
|
+
return `poc-${slug}-${finding.file?.replace(/[\/.]/g, '_') || 'finding'}.${ext}`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function _inferRoute(f) {
|
|
75
|
+
if (f.route) return f.route;
|
|
76
|
+
if (typeof f.vuln === 'string') {
|
|
77
|
+
const m = f.vuln.match(/(?:GET|POST|PUT|DELETE)\s+([\/\w:-]+)/i);
|
|
78
|
+
if (m) return m[1];
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function _inferMethod(f) {
|
|
84
|
+
if (f.method) return f.method;
|
|
85
|
+
if (typeof f.vuln === 'string') {
|
|
86
|
+
const m = f.vuln.match(/\b(GET|POST|PUT|DELETE|PATCH)\b/i);
|
|
87
|
+
if (m) return m[1].toUpperCase();
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function _fallbackPayload(cwe) {
|
|
93
|
+
const fallback = {
|
|
94
|
+
'CWE-89': "1' OR '1'='1",
|
|
95
|
+
'CWE-78': "; rm -rf /tmp/x",
|
|
96
|
+
'CWE-79': "<script>alert(1)</script>",
|
|
97
|
+
'CWE-22': "../../etc/passwd",
|
|
98
|
+
'CWE-918': "http://169.254.169.254/latest/meta-data/",
|
|
99
|
+
'CWE-94': "{{7*7}}",
|
|
100
|
+
'CWE-601': "//evil.example.com/phish",
|
|
101
|
+
'CWE-1321': '{"__proto__":{"polluted":true}}',
|
|
102
|
+
};
|
|
103
|
+
return fallback[cwe] || '<attacker-payload>';
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ─── Templates ───────────────────────────────────────────────────────────
|
|
107
|
+
|
|
108
|
+
function _playwrightTemplate({ finding, baseUrl, route, method, payload }) {
|
|
109
|
+
const safePayload = JSON.stringify(payload);
|
|
110
|
+
return `// Auto-generated PoC by agentic-security ${new Date().toISOString().slice(0,10)}.
|
|
111
|
+
//
|
|
112
|
+
// Finding: ${finding.vuln} (${finding.cwe})
|
|
113
|
+
// Location: ${finding.file}:${finding.line}
|
|
114
|
+
//
|
|
115
|
+
// Run: npx playwright test ${_filenameFor(finding, 'playwright')}
|
|
116
|
+
//
|
|
117
|
+
// Records video to test-results/. The test PASSES when the exploit
|
|
118
|
+
// successfully triggers — flip the assertion if you're auditing the
|
|
119
|
+
// scanner's accuracy.
|
|
120
|
+
|
|
121
|
+
import { test, expect } from '@playwright/test';
|
|
122
|
+
|
|
123
|
+
test('PoC: ${finding.vuln}', async ({ page, request }) => {
|
|
124
|
+
// Phase 1: take a clean baseline screenshot.
|
|
125
|
+
await page.goto('${baseUrl}');
|
|
126
|
+
await page.screenshot({ path: 'test-results/before.png', fullPage: true });
|
|
127
|
+
|
|
128
|
+
// Phase 2: trigger the exploit.
|
|
129
|
+
const PAYLOAD = ${safePayload};
|
|
130
|
+
const response = await request.${method.toLowerCase()}('${baseUrl}${route}', {
|
|
131
|
+
data: { input: PAYLOAD },
|
|
132
|
+
failOnStatusCode: false,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// Phase 3: confirm the exploit fired. The exact assertion depends on
|
|
136
|
+
// the vuln class — adjust before running.
|
|
137
|
+
console.log('Response status:', response.status());
|
|
138
|
+
const body = await response.text();
|
|
139
|
+
console.log('Response (first 500 chars):', body.slice(0, 500));
|
|
140
|
+
|
|
141
|
+
// Phase 4: capture the post-exploit state.
|
|
142
|
+
await page.goto('${baseUrl}${route}');
|
|
143
|
+
await page.screenshot({ path: 'test-results/after.png', fullPage: true });
|
|
144
|
+
|
|
145
|
+
// Default assertion: the response should NOT 4xx-reject the payload.
|
|
146
|
+
// If your sanitizer is in place, this assertion FAILS — which is the
|
|
147
|
+
// outcome you want after applying the fix.
|
|
148
|
+
expect(response.status()).toBeLessThan(400);
|
|
149
|
+
});
|
|
150
|
+
`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function _curlTemplate({ finding, baseUrl, route, method, payload }) {
|
|
154
|
+
return `#!/usr/bin/env bash
|
|
155
|
+
# Auto-generated PoC by agentic-security ${new Date().toISOString().slice(0,10)}.
|
|
156
|
+
#
|
|
157
|
+
# Finding: ${finding.vuln} (${finding.cwe})
|
|
158
|
+
# Location: ${finding.file}:${finding.line}
|
|
159
|
+
#
|
|
160
|
+
# Run: bash ${_filenameFor(finding, 'curl')}
|
|
161
|
+
#
|
|
162
|
+
# Exits 0 if the exploit appears to trigger; non-zero otherwise.
|
|
163
|
+
# Flip the assertion if you're auditing the scanner's accuracy.
|
|
164
|
+
|
|
165
|
+
set -euo pipefail
|
|
166
|
+
|
|
167
|
+
BASE_URL="\${BASE_URL:-${baseUrl}}"
|
|
168
|
+
ROUTE="${route}"
|
|
169
|
+
METHOD="${method}"
|
|
170
|
+
PAYLOAD=${JSON.stringify(payload)}
|
|
171
|
+
|
|
172
|
+
echo "→ Sending $METHOD $BASE_URL$ROUTE"
|
|
173
|
+
echo " Payload: $PAYLOAD"
|
|
174
|
+
echo
|
|
175
|
+
|
|
176
|
+
RESPONSE=$(curl -sS -X "$METHOD" \\
|
|
177
|
+
-H "Content-Type: application/json" \\
|
|
178
|
+
-d "{\\"input\\":$(printf %s "$PAYLOAD" | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()))')}" \\
|
|
179
|
+
-w "\\nHTTP_STATUS:%{http_code}\\n" \\
|
|
180
|
+
"$BASE_URL$ROUTE" || true)
|
|
181
|
+
|
|
182
|
+
STATUS=$(echo "$RESPONSE" | grep '^HTTP_STATUS:' | cut -d: -f2)
|
|
183
|
+
BODY=$(echo "$RESPONSE" | sed '/^HTTP_STATUS:/d')
|
|
184
|
+
|
|
185
|
+
echo "← HTTP $STATUS"
|
|
186
|
+
echo "$BODY" | head -20
|
|
187
|
+
echo
|
|
188
|
+
|
|
189
|
+
# Default assertion: 2xx means the payload was accepted (i.e., the
|
|
190
|
+
# vulnerable path was reached). After fixing the vuln, this should 4xx.
|
|
191
|
+
if [ "$STATUS" -lt 400 ]; then
|
|
192
|
+
echo "❌ PAYLOAD ACCEPTED ($STATUS) — vuln likely still present"
|
|
193
|
+
exit 1
|
|
194
|
+
else
|
|
195
|
+
echo "✓ Payload rejected ($STATUS) — sanitizer appears to be working"
|
|
196
|
+
exit 0
|
|
197
|
+
fi
|
|
198
|
+
`;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function _httpTemplate({ finding, baseUrl, route, method, payload }) {
|
|
202
|
+
const url = new URL(route, baseUrl);
|
|
203
|
+
return `# Auto-generated PoC by agentic-security ${new Date().toISOString().slice(0,10)}.
|
|
204
|
+
# Finding: ${finding.vuln} (${finding.cwe})
|
|
205
|
+
# Location: ${finding.file}:${finding.line}
|
|
206
|
+
#
|
|
207
|
+
# Paste this into any HTTP client (Postman, Insomnia, vscode-rest-client).
|
|
208
|
+
|
|
209
|
+
${method} ${url.pathname}${url.search} HTTP/1.1
|
|
210
|
+
Host: ${url.host}
|
|
211
|
+
User-Agent: agentic-security-poc/1.0
|
|
212
|
+
Content-Type: application/json
|
|
213
|
+
Accept: application/json
|
|
214
|
+
|
|
215
|
+
{"input": ${JSON.stringify(payload)}}
|
|
216
|
+
|
|
217
|
+
###
|
|
218
|
+
|
|
219
|
+
# Expected outcome (pre-fix): server returns 2xx + vulnerable payload
|
|
220
|
+
# reaches the sink. After fix: server returns 4xx with a validation
|
|
221
|
+
# error. The CWE-specific signature to look for in the response:
|
|
222
|
+
#
|
|
223
|
+
${_responseSignatureFor(finding.cwe)}
|
|
224
|
+
`;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function _responseSignatureFor(cwe) {
|
|
228
|
+
const sigs = {
|
|
229
|
+
'CWE-89': '# - SQL error containing "syntax" / "quote" / "OR"',
|
|
230
|
+
'CWE-78': '# - shell output of the injected command in body',
|
|
231
|
+
'CWE-79': '# - <script> tag echoed back into HTML response body',
|
|
232
|
+
'CWE-22': '# - file contents from outside the intended directory',
|
|
233
|
+
'CWE-918': '# - response body from the metadata IP',
|
|
234
|
+
'CWE-601': '# - Location: header pointing to the attacker domain',
|
|
235
|
+
};
|
|
236
|
+
return sigs[cwe] || '# - unexpected response shape consistent with the vuln class';
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export const _internal = { _defaultFormatFor, _inferRoute, _inferMethod, _fallbackPayload, _playwrightTemplate, _curlTemplate, _httpTemplate };
|