mindforge-cc 11.8.2 → 11.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/.agent/mindforge/wf-orchestrate-review.md +35 -0
- package/.agent/mindforge/wf-verification-loop.md +45 -0
- package/.agent/mindforge/wf-workflow-optimizer.md +51 -0
- package/.agent/skills/article-illustrator/SKILL.md +7 -7
- package/.agent/skills/arxiv/SKILL.md +6 -6
- package/.agent/skills/code-wiki/SKILL.md +11 -11
- package/.agent/skills/concept-diagrams/SKILL.md +6 -6
- package/.agent/skills/concept-diagrams/examples/apartment-floor-plan-conversion.md +244 -0
- package/.agent/skills/concept-diagrams/examples/automated-password-reset-flow.md +276 -0
- package/.agent/skills/concept-diagrams/examples/autonomous-llm-research-agent-flow.md +240 -0
- package/.agent/skills/concept-diagrams/examples/banana-journey-tree-to-smoothie.md +161 -0
- package/.agent/skills/concept-diagrams/examples/commercial-aircraft-structure.md +209 -0
- package/.agent/skills/concept-diagrams/examples/cpu-ooo-microarchitecture.md +236 -0
- package/.agent/skills/concept-diagrams/examples/electricity-grid-flow.md +182 -0
- package/.agent/skills/concept-diagrams/examples/feature-film-production-pipeline.md +172 -0
- package/.agent/skills/concept-diagrams/examples/hospital-emergency-department-flow.md +165 -0
- package/.agent/skills/concept-diagrams/examples/ml-benchmark-grouped-bar-chart.md +114 -0
- package/.agent/skills/concept-diagrams/examples/place-order-uml-sequence.md +325 -0
- package/.agent/skills/concept-diagrams/examples/smart-city-infrastructure.md +173 -0
- package/.agent/skills/concept-diagrams/examples/smartphone-layer-anatomy.md +154 -0
- package/.agent/skills/concept-diagrams/examples/sn2-reaction-mechanism.md +247 -0
- package/.agent/skills/concept-diagrams/examples/wind-turbine-structure.md +338 -0
- package/.agent/skills/concept-diagrams/templates/template.html +174 -0
- package/.agent/skills/domain-intel/SKILL.md +7 -7
- package/.agent/skills/duckduckgo-search/SKILL.md +17 -17
- package/.agent/skills/github-auth/scripts/gh-env.sh +66 -0
- package/.agent/skills/github-code-review/SKILL.md +9 -9
- package/.agent/skills/github-pr-workflow/SKILL.md +3 -3
- package/.agent/skills/godmode/SKILL.md +8 -8
- package/.agent/skills/jupyter-live-kernel/SKILL.md +3 -3
- package/.agent/skills/kanban-orchestrator/SKILL.md +10 -10
- package/.agent/skills/kanban-worker/SKILL.md +6 -6
- package/.agent/skills/llm-wiki/SKILL.md +7 -7
- package/.agent/skills/mindforge-autonomous/SKILL.md +1 -1
- package/.agent/skills/mindforge-complete-milestone/SKILL.md +1 -1
- package/.agent/skills/mindforge-discuss-phase/SKILL.md +1 -1
- package/.agent/skills/mindforge-do/SKILL.md +1 -1
- package/.agent/skills/mindforge-execute-phase/SKILL.md +1 -1
- package/.agent/skills/mindforge-list-workspaces/SKILL.md +1 -1
- package/.agent/skills/mindforge-manager/SKILL.md +1 -1
- package/.agent/skills/mindforge-neural-orchestrator/SKILL.md +1 -1
- package/.agent/skills/mindforge-new-milestone/SKILL.md +4 -4
- package/.agent/skills/mindforge-new-project/SKILL.md +4 -4
- package/.agent/skills/mindforge-new-workspace/SKILL.md +1 -1
- package/.agent/skills/mindforge-note/SKILL.md +1 -1
- package/.agent/skills/mindforge-plan-phase/SKILL.md +1 -1
- package/.agent/skills/mindforge-profile-user/SKILL.md +1 -1
- package/.agent/skills/mindforge-remove-workspace/SKILL.md +1 -1
- package/.agent/skills/mindforge-research-phase/SKILL.md +1 -1
- package/.agent/skills/mindforge-ui-phase/SKILL.md +1 -1
- package/.agent/skills/mindforge-ui-review/SKILL.md +1 -1
- package/.agent/skills/mindforge-verify-work/SKILL.md +1 -1
- package/.agent/skills/mindforge-workspace-isolated/SKILL.md +1 -1
- package/.agent/skills/node-inspect-debugger/SKILL.md +1 -1
- package/.agent/skills/obsidian/SKILL.md +15 -15
- package/.agent/skills/osint-investigation/SKILL.md +3 -2
- package/.agent/skills/osint-investigation/references/sources/courtlistener.md +98 -0
- package/.agent/skills/osint-investigation/references/sources/gdelt.md +104 -0
- package/.agent/skills/osint-investigation/references/sources/icij-offshore.md +104 -0
- package/.agent/skills/osint-investigation/references/sources/nyc-acris.md +90 -0
- package/.agent/skills/osint-investigation/references/sources/ofac-sdn.md +92 -0
- package/.agent/skills/osint-investigation/references/sources/opencorporates.md +103 -0
- package/.agent/skills/osint-investigation/references/sources/sec-edgar.md +83 -0
- package/.agent/skills/osint-investigation/references/sources/senate-ld.md +89 -0
- package/.agent/skills/osint-investigation/references/sources/usaspending.md +97 -0
- package/.agent/skills/osint-investigation/references/sources/wayback.md +93 -0
- package/.agent/skills/osint-investigation/references/sources/wikipedia.md +107 -0
- package/.agent/skills/osint-investigation/scripts/_http.py +82 -0
- package/.agent/skills/osint-investigation/scripts/_normalize.py +67 -0
- package/.agent/skills/osint-investigation/scripts/build_findings.py +221 -0
- package/.agent/skills/osint-investigation/scripts/entity_resolution.py +228 -0
- package/.agent/skills/osint-investigation/scripts/fetch_courtlistener.py +149 -0
- package/.agent/skills/osint-investigation/scripts/fetch_gdelt.py +161 -0
- package/.agent/skills/osint-investigation/scripts/fetch_icij_offshore.py +234 -0
- package/.agent/skills/osint-investigation/scripts/fetch_nyc_acris.py +203 -0
- package/.agent/skills/osint-investigation/scripts/fetch_ofac_sdn.py +175 -0
- package/.agent/skills/osint-investigation/scripts/fetch_opencorporates.py +191 -0
- package/.agent/skills/osint-investigation/scripts/fetch_sec_edgar.py +184 -0
- package/.agent/skills/osint-investigation/scripts/fetch_senate_ld.py +146 -0
- package/.agent/skills/osint-investigation/scripts/fetch_usaspending.py +170 -0
- package/.agent/skills/osint-investigation/scripts/fetch_wayback.py +142 -0
- package/.agent/skills/osint-investigation/scripts/fetch_wikipedia.py +266 -0
- package/.agent/skills/osint-investigation/scripts/timing_analysis.py +252 -0
- package/.agent/skills/oss-forensics/SKILL.md +10 -9
- package/.agent/skills/oss-forensics/scripts/evidence-store.py +313 -0
- package/.agent/skills/parallel-cli/SKILL.md +2 -2
- package/.agent/skills/pinggy-tunnel/SKILL.md +3 -3
- package/.agent/skills/pixel-art/SKILL.md +2 -2
- package/.agent/skills/plan/SKILL.md +10 -10
- package/.agent/skills/requesting-code-review/SKILL.md +4 -6
- package/.agent/skills/research-paper-writing/SKILL.md +32 -32
- package/.agent/skills/rest-graphql-debug/SKILL.md +31 -32
- package/.agent/skills/s6-container-supervision/SKILL.md +1 -1
- package/.agent/skills/scrapling/SKILL.md +1 -1
- package/.agent/skills/sherlock/SKILL.md +1 -1
- package/.agent/skills/simplify-code/SKILL.md +5 -7
- package/.agent/skills/skill-authoring/SKILL.md +4 -4
- package/.agent/skills/spike/SKILL.md +11 -13
- package/.agent/skills/subagent-driven-development/SKILL.md +7 -11
- package/.agent/skills/systematic-debugging/SKILL.md +13 -14
- package/.agent/skills/test-driven-development/SKILL.md +6 -7
- package/.agent/skills/video-orchestrator/SKILL.md +1 -1
- package/.agent/skills/web-pentest/SKILL.md +7 -7
- package/.mindforge/config.json +3 -2
- package/.mindforge/dynamic-workflows/REGISTRY.md +4 -1
- package/.mindforge/dynamic-workflows/index.json +910 -840
- package/.mindforge/dynamic-workflows/scripts/accessibility-audit.js +88 -90
- package/.mindforge/dynamic-workflows/scripts/ai-model-eval.js +59 -61
- package/.mindforge/dynamic-workflows/scripts/api-contract-test.js +87 -85
- package/.mindforge/dynamic-workflows/scripts/api-migration.js +120 -122
- package/.mindforge/dynamic-workflows/scripts/architecture-modernization.js +84 -86
- package/.mindforge/dynamic-workflows/scripts/code-audit.js +75 -77
- package/.mindforge/dynamic-workflows/scripts/code-explainer.js +99 -101
- package/.mindforge/dynamic-workflows/scripts/competitive-analysis.js +62 -64
- package/.mindforge/dynamic-workflows/scripts/competitive-teardown.js +106 -108
- package/.mindforge/dynamic-workflows/scripts/cost-analysis.js +78 -80
- package/.mindforge/dynamic-workflows/scripts/data-pipeline-validate.js +47 -49
- package/.mindforge/dynamic-workflows/scripts/database-migration.js +85 -87
- package/.mindforge/dynamic-workflows/scripts/debug-detective.js +97 -99
- package/.mindforge/dynamic-workflows/scripts/dependency-health.js +81 -83
- package/.mindforge/dynamic-workflows/scripts/design-system-audit.js +84 -86
- package/.mindforge/dynamic-workflows/scripts/documentation-gen.js +65 -67
- package/.mindforge/dynamic-workflows/scripts/feature-planner.js +78 -80
- package/.mindforge/dynamic-workflows/scripts/incident-response.js +81 -83
- package/.mindforge/dynamic-workflows/scripts/multi-repo-sync.js +49 -51
- package/.mindforge/dynamic-workflows/scripts/mutation-testing.js +113 -115
- package/.mindforge/dynamic-workflows/scripts/onboard-codebase.js +79 -81
- package/.mindforge/dynamic-workflows/scripts/orchestrate-review.js +115 -0
- package/.mindforge/dynamic-workflows/scripts/perf-optimize.js +97 -99
- package/.mindforge/dynamic-workflows/scripts/pr-review.js +61 -63
- package/.mindforge/dynamic-workflows/scripts/refactor-plan.js +88 -90
- package/.mindforge/dynamic-workflows/scripts/release-prep.js +79 -81
- package/.mindforge/dynamic-workflows/scripts/security-hardening.js +117 -119
- package/.mindforge/dynamic-workflows/scripts/security-threat-model.js +119 -121
- package/.mindforge/dynamic-workflows/scripts/tdd-sprint.js +80 -82
- package/.mindforge/dynamic-workflows/scripts/tech-evaluation.js +50 -52
- package/.mindforge/dynamic-workflows/scripts/test-coverage-gap.js +68 -70
- package/.mindforge/dynamic-workflows/scripts/ux-heuristic-audit.js +89 -91
- package/.mindforge/dynamic-workflows/scripts/verification-loop.js +138 -0
- package/.mindforge/dynamic-workflows/scripts/workflow-optimizer.js +136 -0
- package/.mindforge/dynamic-workflows/scripts/writer-reviewer.js +63 -65
- package/.mindforge/memory/sync-manifest.json +1 -1
- package/.mindforge/personas/advisor-researcher.md +2 -2
- package/.mindforge/personas/api-load-tester.md +1 -1
- package/.mindforge/personas/authentication-architect.md +1 -1
- package/.mindforge/personas/backup-recovery-specialist.md +1 -1
- package/.mindforge/personas/build-optimizer.md +1 -1
- package/.mindforge/personas/caching-strategist.md +1 -1
- package/.mindforge/personas/chaos-engineer.md +1 -1
- package/.mindforge/personas/compliance-auditor.md +1 -1
- package/.mindforge/personas/config-management-expert.md +1 -1
- package/.mindforge/personas/coverage-specialist.md +1 -1
- package/.mindforge/personas/debug-specialist.md +2 -2
- package/.mindforge/personas/debugger.md +2 -2
- package/.mindforge/personas/dependency-auditor.md +1 -1
- package/.mindforge/personas/developer.md +1 -1
- package/.mindforge/personas/devops-engineer.md +1 -1
- package/.mindforge/personas/executor.md +1 -1
- package/.mindforge/personas/go-specialist.md +1 -1
- package/.mindforge/personas/incident-commander.md +1 -1
- package/.mindforge/personas/java-specialist.md +1 -1
- package/.mindforge/personas/kubernetes-debugger.md +1 -1
- package/.mindforge/personas/logging-architect.md +1 -1
- package/.mindforge/personas/mf-planner.md +1 -1
- package/.mindforge/personas/mf-researcher.md +1 -1
- package/.mindforge/personas/mf-reviewer.md +1 -1
- package/.mindforge/personas/nyquist-auditor.md +1 -1
- package/.mindforge/personas/observability-engineer.md +1 -1
- package/.mindforge/personas/performance-optimizer.md +1 -1
- package/.mindforge/personas/phase-researcher.md +5 -5
- package/.mindforge/personas/planner.md +1 -1
- package/.mindforge/personas/project-researcher.md +2 -2
- package/.mindforge/personas/python-specialist.md +1 -1
- package/.mindforge/personas/qa-engineer.md +1 -1
- package/.mindforge/personas/release-manager.md +1 -1
- package/.mindforge/personas/research-agent.md +4 -4
- package/.mindforge/personas/roadmapper-extend.md +1 -1
- package/.mindforge/personas/rust-specialist.md +1 -1
- package/.mindforge/personas/security-reviewer.md +1 -1
- package/.mindforge/personas/tech-writer.md +1 -1
- package/.mindforge/personas/typescript-wizard.md +1 -1
- package/.mindforge/personas/ui-researcher.md +1 -1
- package/.mindforge/skills/accessibility/SKILL.md +3 -3
- package/.mindforge/skills/agent-architecture-audit/SKILL.md +11 -0
- package/.mindforge/skills/agent-loops/SKILL.md +1 -1
- package/.mindforge/skills/arxiv/SKILL.md +6 -6
- package/.mindforge/skills/arxiv/scripts/search_arxiv.py +114 -0
- package/.mindforge/skills/code-tour/SKILL.md +2 -2
- package/.mindforge/skills/code-wiki/SKILL.md +11 -11
- package/.mindforge/skills/code-wiki/templates/README.md +31 -0
- package/.mindforge/skills/code-wiki/templates/architecture.md +30 -0
- package/.mindforge/skills/code-wiki/templates/getting-started.md +47 -0
- package/.mindforge/skills/code-wiki/templates/module.md +38 -0
- package/.mindforge/skills/concept-diagrams/SKILL.md +6 -6
- package/.mindforge/skills/concept-diagrams/examples/apartment-floor-plan-conversion.md +244 -0
- package/.mindforge/skills/concept-diagrams/examples/automated-password-reset-flow.md +276 -0
- package/.mindforge/skills/concept-diagrams/examples/autonomous-llm-research-agent-flow.md +240 -0
- package/.mindforge/skills/concept-diagrams/examples/banana-journey-tree-to-smoothie.md +161 -0
- package/.mindforge/skills/concept-diagrams/examples/commercial-aircraft-structure.md +209 -0
- package/.mindforge/skills/concept-diagrams/examples/cpu-ooo-microarchitecture.md +236 -0
- package/.mindforge/skills/concept-diagrams/examples/electricity-grid-flow.md +182 -0
- package/.mindforge/skills/concept-diagrams/examples/feature-film-production-pipeline.md +172 -0
- package/.mindforge/skills/concept-diagrams/examples/hospital-emergency-department-flow.md +165 -0
- package/.mindforge/skills/concept-diagrams/examples/ml-benchmark-grouped-bar-chart.md +114 -0
- package/.mindforge/skills/concept-diagrams/examples/place-order-uml-sequence.md +325 -0
- package/.mindforge/skills/concept-diagrams/examples/smart-city-infrastructure.md +173 -0
- package/.mindforge/skills/concept-diagrams/examples/smartphone-layer-anatomy.md +154 -0
- package/.mindforge/skills/concept-diagrams/examples/sn2-reaction-mechanism.md +247 -0
- package/.mindforge/skills/concept-diagrams/examples/wind-turbine-structure.md +338 -0
- package/.mindforge/skills/concept-diagrams/references/dashboard-patterns.md +43 -0
- package/.mindforge/skills/concept-diagrams/references/infrastructure-patterns.md +144 -0
- package/.mindforge/skills/concept-diagrams/references/physical-shape-cookbook.md +42 -0
- package/.mindforge/skills/concept-diagrams/templates/template.html +174 -0
- package/.mindforge/skills/creative-ideation/references/full-prompt-library.md +110 -0
- package/.mindforge/skills/documentation/SKILL.md +1 -1
- package/.mindforge/skills/domain-intel/SKILL.md +7 -7
- package/.mindforge/skills/domain-intel/scripts/domain_intel.py +397 -0
- package/.mindforge/skills/duckduckgo-search/SKILL.md +17 -17
- package/.mindforge/skills/duckduckgo-search/scripts/duckduckgo.sh +28 -0
- package/.mindforge/skills/environment-management/SKILL.md +1 -1
- package/.mindforge/skills/github-code-review/SKILL.md +9 -9
- package/.mindforge/skills/github-code-review/references/review-output-template.md +74 -0
- package/.mindforge/skills/github-issues/templates/bug-report.md +35 -0
- package/.mindforge/skills/github-issues/templates/feature-request.md +31 -0
- package/.mindforge/skills/github-pr-workflow/SKILL.md +3 -3
- package/.mindforge/skills/github-pr-workflow/references/ci-troubleshooting.md +183 -0
- package/.mindforge/skills/github-pr-workflow/references/conventional-commits.md +71 -0
- package/.mindforge/skills/github-pr-workflow/templates/pr-body-bugfix.md +35 -0
- package/.mindforge/skills/github-pr-workflow/templates/pr-body-feature.md +33 -0
- package/.mindforge/skills/incident-response/SKILL.md +1 -1
- package/.mindforge/skills/jupyter-live-kernel/SKILL.md +3 -3
- package/.mindforge/skills/kanban-orchestrator/SKILL.md +10 -10
- package/.mindforge/skills/kanban-worker/SKILL.md +6 -6
- package/.mindforge/skills/meme-generation/SKILL.md +1 -1
- package/.mindforge/skills/meme-generation/scripts/generate_meme.py +470 -0
- package/.mindforge/skills/meme-generation/scripts/templates.json +97 -0
- package/.mindforge/skills/obsidian/SKILL.md +15 -15
- package/.mindforge/skills/orch-pipeline/SKILL.md +2 -2
- package/.mindforge/skills/osint-investigation/SKILL.md +3 -2
- package/.mindforge/skills/osint-investigation/references/sources/courtlistener.md +98 -0
- package/.mindforge/skills/osint-investigation/references/sources/gdelt.md +104 -0
- package/.mindforge/skills/osint-investigation/references/sources/icij-offshore.md +104 -0
- package/.mindforge/skills/osint-investigation/references/sources/nyc-acris.md +90 -0
- package/.mindforge/skills/osint-investigation/references/sources/ofac-sdn.md +92 -0
- package/.mindforge/skills/osint-investigation/references/sources/opencorporates.md +103 -0
- package/.mindforge/skills/osint-investigation/references/sources/sec-edgar.md +83 -0
- package/.mindforge/skills/osint-investigation/references/sources/senate-ld.md +89 -0
- package/.mindforge/skills/osint-investigation/references/sources/usaspending.md +97 -0
- package/.mindforge/skills/osint-investigation/references/sources/wayback.md +93 -0
- package/.mindforge/skills/osint-investigation/references/sources/wikipedia.md +107 -0
- package/.mindforge/skills/osint-investigation/scripts/_http.py +82 -0
- package/.mindforge/skills/osint-investigation/scripts/_normalize.py +67 -0
- package/.mindforge/skills/osint-investigation/scripts/build_findings.py +221 -0
- package/.mindforge/skills/osint-investigation/scripts/entity_resolution.py +228 -0
- package/.mindforge/skills/osint-investigation/scripts/fetch_courtlistener.py +149 -0
- package/.mindforge/skills/osint-investigation/scripts/fetch_gdelt.py +161 -0
- package/.mindforge/skills/osint-investigation/scripts/fetch_icij_offshore.py +234 -0
- package/.mindforge/skills/osint-investigation/scripts/fetch_nyc_acris.py +203 -0
- package/.mindforge/skills/osint-investigation/scripts/fetch_ofac_sdn.py +175 -0
- package/.mindforge/skills/osint-investigation/scripts/fetch_opencorporates.py +191 -0
- package/.mindforge/skills/osint-investigation/scripts/fetch_sec_edgar.py +184 -0
- package/.mindforge/skills/osint-investigation/scripts/fetch_senate_ld.py +146 -0
- package/.mindforge/skills/osint-investigation/scripts/fetch_usaspending.py +170 -0
- package/.mindforge/skills/osint-investigation/scripts/fetch_wayback.py +142 -0
- package/.mindforge/skills/osint-investigation/scripts/fetch_wikipedia.py +266 -0
- package/.mindforge/skills/osint-investigation/scripts/timing_analysis.py +252 -0
- package/.mindforge/skills/osint-investigation/templates/source-template.md +59 -0
- package/.mindforge/skills/oss-forensics/SKILL.md +6 -6
- package/.mindforge/skills/oss-forensics/references/evidence-types.md +89 -0
- package/.mindforge/skills/oss-forensics/references/github-archive-guide.md +184 -0
- package/.mindforge/skills/oss-forensics/references/investigation-templates.md +131 -0
- package/.mindforge/skills/oss-forensics/references/recovery-techniques.md +164 -0
- package/.mindforge/skills/oss-forensics/scripts/evidence-store.py +313 -0
- package/.mindforge/skills/oss-forensics/templates/forensic-report.md +151 -0
- package/.mindforge/skills/oss-forensics/templates/malicious-package-report.md +43 -0
- package/.mindforge/skills/pixel-art/SKILL.md +2 -2
- package/.mindforge/skills/pixel-art/references/palettes.md +49 -0
- package/.mindforge/skills/plan/SKILL.md +10 -10
- package/.mindforge/skills/requesting-code-review/SKILL.md +4 -6
- package/.mindforge/skills/research-paper-writing/SKILL.md +32 -32
- package/.mindforge/skills/research-paper-writing/references/autoreason-methodology.md +394 -0
- package/.mindforge/skills/research-paper-writing/references/checklists.md +434 -0
- package/.mindforge/skills/research-paper-writing/references/citation-workflow.md +563 -0
- package/.mindforge/skills/research-paper-writing/references/experiment-patterns.md +728 -0
- package/.mindforge/skills/research-paper-writing/references/human-evaluation.md +476 -0
- package/.mindforge/skills/research-paper-writing/references/paper-types.md +481 -0
- package/.mindforge/skills/research-paper-writing/references/reviewer-guidelines.md +433 -0
- package/.mindforge/skills/research-paper-writing/references/sources.md +191 -0
- package/.mindforge/skills/research-paper-writing/references/writing-guide.md +474 -0
- package/.mindforge/skills/research-paper-writing/templates/README.md +251 -0
- package/.mindforge/skills/scrapling/SKILL.md +1 -1
- package/.mindforge/skills/security-review/SKILL.md +1 -1
- package/.mindforge/skills/sherlock/SKILL.md +1 -1
- package/.mindforge/skills/simplify-code/SKILL.md +5 -7
- package/.mindforge/skills/spike/SKILL.md +11 -13
- package/.mindforge/skills/subagent-driven-development/SKILL.md +7 -11
- package/.mindforge/skills/subagent-driven-development/references/context-budget-discipline.md +53 -0
- package/.mindforge/skills/subagent-driven-development/references/gates-taxonomy.md +93 -0
- package/.mindforge/skills/systematic-debugging/SKILL.md +13 -14
- package/.mindforge/skills/test-driven-development/SKILL.md +7 -8
- package/.mindforge/skills/testing-standards/SKILL.md +1 -1
- package/.mindforge/skills/web-pentest/SKILL.md +2 -2
- package/.mindforge/skills/web-pentest/references/bypass-techniques.md +133 -0
- package/.mindforge/skills/web-pentest/references/exploitation-techniques.md +204 -0
- package/.mindforge/skills/web-pentest/references/scope-enforcement.md +110 -0
- package/.mindforge/skills/web-pentest/references/vuln-taxonomy.md +81 -0
- package/.mindforge/skills/web-pentest/templates/authorization.md +69 -0
- package/.mindforge/skills/web-pentest/templates/pentest-report.md +178 -0
- package/.mindforge/skills/writing-skills/SKILL.md +4 -3
- package/CHANGELOG.md +35 -0
- package/MINDFORGE.md +3 -3
- package/README.md +9 -7
- package/RELEASENOTES.md +65 -2
- package/SECURITY.md +5 -3
- package/bin/autonomous/auto-runner.js +5 -2
- package/bin/engine/skill-loader.js +4 -0
- package/bin/governance/rbac.js +4 -0
- package/bin/memory/eis-client.js +1 -0
- package/bin/mindforge-cli.js +5 -0
- package/bin/models/bedrock-provider.js +192 -0
- package/bin/models/model-client.js +19 -0
- package/bin/spawn-agent.js +9 -2
- package/docs/commands-reference.md +68 -6
- package/docs/faq.md +38 -1
- package/docs/getting-started.md +27 -12
- package/docs/sdk-reference.md +36 -3
- package/docs/troubleshooting.md +59 -1
- package/docs/user-guide.md +7 -4
- package/package.json +3 -3
- package/.claude/commands/mindforge/skill-tdd.md +0 -53
- package/.claude/commands/mindforge/skills-index.md +0 -118
- package/.claude/commands/mindforge/systematic-debug.md +0 -60
- package/.claude/commands/mindforge/wf-accessibility-audit.md +0 -31
- package/.claude/commands/mindforge/wf-ai-model-eval.md +0 -31
- package/.claude/commands/mindforge/wf-api-migration.md +0 -31
- package/.claude/commands/mindforge/wf-architecture-modernization.md +0 -32
- package/.claude/commands/mindforge/wf-catalog.md +0 -59
- package/.claude/commands/mindforge/wf-code-audit.md +0 -31
- package/.claude/commands/mindforge/wf-code-explainer.md +0 -31
- package/.claude/commands/mindforge/wf-competitive-analysis.md +0 -31
- package/.claude/commands/mindforge/wf-competitive-teardown.md +0 -31
- package/.claude/commands/mindforge/wf-cost-analysis.md +0 -31
- package/.claude/commands/mindforge/wf-data-pipeline-validate.md +0 -31
- package/.claude/commands/mindforge/wf-database-migration.md +0 -31
- package/.claude/commands/mindforge/wf-debug-detective.md +0 -32
- package/.claude/commands/mindforge/wf-design-system-audit.md +0 -31
- package/.claude/commands/mindforge/wf-documentation-gen.md +0 -31
- package/.claude/commands/mindforge/wf-feature-planner.md +0 -31
- package/.claude/commands/mindforge/wf-incident-response.md +0 -31
- package/.claude/commands/mindforge/wf-multi-repo-sync.md +0 -31
- package/.claude/commands/mindforge/wf-mutation-testing.md +0 -31
- package/.claude/commands/mindforge/wf-onboard-codebase.md +0 -31
- package/.claude/commands/mindforge/wf-perf-optimize.md +0 -31
- package/.claude/commands/mindforge/wf-pr-review.md +0 -31
- package/.claude/commands/mindforge/wf-refactor-plan.md +0 -31
- package/.claude/commands/mindforge/wf-release-prep.md +0 -31
- package/.claude/commands/mindforge/wf-security-threat-model.md +0 -31
- package/.claude/commands/mindforge/wf-tdd-sprint.md +0 -31
- package/.claude/commands/mindforge/wf-tech-evaluation.md +0 -31
- package/.claude/commands/mindforge/wf-ux-heuristic-audit.md +0 -31
- package/.claude/commands/mindforge/wf-writer-reviewer.md +0 -30
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Permutation test for donation/contract timing correlation (stdlib-only).
|
|
3
|
+
|
|
4
|
+
For each (donor, vendor) pair, compute the mean number of days between each
|
|
5
|
+
donation and the nearest contract award. Then shuffle contract award dates
|
|
6
|
+
N times within the observation window and compute the same statistic. The
|
|
7
|
+
one-tailed p-value is the fraction of permutations whose mean is <= the
|
|
8
|
+
observed mean (smaller distance = tighter clustering).
|
|
9
|
+
|
|
10
|
+
Adapted from ShinMegamiBoson/OpenPlanter (MIT). Differences:
|
|
11
|
+
- Pure stdlib (no pandas / numpy)
|
|
12
|
+
- Domain-agnostic (no snow-vendor / CRITICAL-politician filter)
|
|
13
|
+
- Configurable column names via flags
|
|
14
|
+
- Optional --seed for reproducibility
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import argparse
|
|
19
|
+
import csv
|
|
20
|
+
import datetime as dt
|
|
21
|
+
import json
|
|
22
|
+
import random
|
|
23
|
+
import statistics
|
|
24
|
+
from collections import defaultdict
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
|
|
27
|
+
_DATE_FORMATS = ("%Y-%m-%d", "%m/%d/%Y", "%Y/%m/%d", "%m-%d-%Y", "%Y%m%d")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def parse_date(raw: str) -> dt.date | None:
|
|
31
|
+
if not raw:
|
|
32
|
+
return None
|
|
33
|
+
raw = raw.strip()
|
|
34
|
+
for fmt in _DATE_FORMATS:
|
|
35
|
+
try:
|
|
36
|
+
return dt.datetime.strptime(raw, fmt).date()
|
|
37
|
+
except ValueError:
|
|
38
|
+
continue
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _read(path: str) -> list[dict[str, str]]:
|
|
43
|
+
with open(path, newline="", encoding="utf-8") as fh:
|
|
44
|
+
return list(csv.DictReader(fh))
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _nearest_distance(donation_date: dt.date, awards: list[dt.date]) -> int:
|
|
48
|
+
"""Absolute days to nearest award date."""
|
|
49
|
+
return min(abs((donation_date - a).days) for a in awards)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _permute(
|
|
53
|
+
awards_count: int,
|
|
54
|
+
donations: list[dt.date],
|
|
55
|
+
date_min: dt.date,
|
|
56
|
+
date_max: dt.date,
|
|
57
|
+
rng: random.Random,
|
|
58
|
+
) -> float:
|
|
59
|
+
"""One permutation: draw uniform random award dates, compute mean nearest-distance."""
|
|
60
|
+
span_days = (date_max - date_min).days or 1
|
|
61
|
+
rand_awards = [
|
|
62
|
+
date_min + dt.timedelta(days=rng.randint(0, span_days))
|
|
63
|
+
for _ in range(awards_count)
|
|
64
|
+
]
|
|
65
|
+
distances = [_nearest_distance(d, rand_awards) for d in donations]
|
|
66
|
+
return statistics.mean(distances)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def analyze(
|
|
70
|
+
donations_path: str,
|
|
71
|
+
donation_date_col: str,
|
|
72
|
+
donation_amount_col: str,
|
|
73
|
+
donation_donor_col: str,
|
|
74
|
+
donation_recipient_col: str,
|
|
75
|
+
contracts_path: str,
|
|
76
|
+
contract_date_col: str,
|
|
77
|
+
contract_vendor_col: str,
|
|
78
|
+
cross_links_path: str | None,
|
|
79
|
+
n_permutations: int = 1000,
|
|
80
|
+
min_donations: int = 3,
|
|
81
|
+
p_threshold: float = 0.05,
|
|
82
|
+
seed: int | None = None,
|
|
83
|
+
out_path: str = "timing.json",
|
|
84
|
+
) -> dict:
|
|
85
|
+
rng = random.Random(seed)
|
|
86
|
+
|
|
87
|
+
donations = _read(donations_path)
|
|
88
|
+
contracts = _read(contracts_path)
|
|
89
|
+
|
|
90
|
+
# Allow optional join through cross_links — donor (left) ↔ vendor (right).
|
|
91
|
+
# When present, donor strings get mapped to matched vendor names so the
|
|
92
|
+
# vendor-date index lookup actually finds the contracts.
|
|
93
|
+
matched_pairs: set[tuple[str, str]] | None = None
|
|
94
|
+
donor_to_vendors: dict[str, set[str]] = defaultdict(set)
|
|
95
|
+
if cross_links_path:
|
|
96
|
+
matched_pairs = set()
|
|
97
|
+
for row in _read(cross_links_path):
|
|
98
|
+
left = row.get("left_name", "")
|
|
99
|
+
right = row.get("right_name", "")
|
|
100
|
+
matched_pairs.add((left, right))
|
|
101
|
+
donor_to_vendors[left].add(right)
|
|
102
|
+
|
|
103
|
+
# Index contract dates by vendor name.
|
|
104
|
+
vendor_to_award_dates: dict[str, list[dt.date]] = defaultdict(list)
|
|
105
|
+
all_award_dates: list[dt.date] = []
|
|
106
|
+
for row in contracts:
|
|
107
|
+
d = parse_date(row.get(contract_date_col, ""))
|
|
108
|
+
if not d:
|
|
109
|
+
continue
|
|
110
|
+
vendor_to_award_dates[row.get(contract_vendor_col, "").strip()].append(d)
|
|
111
|
+
all_award_dates.append(d)
|
|
112
|
+
|
|
113
|
+
if not all_award_dates:
|
|
114
|
+
raise SystemExit(f"No parseable dates in {contracts_path}/{contract_date_col}")
|
|
115
|
+
global_min = min(all_award_dates)
|
|
116
|
+
global_max = max(all_award_dates)
|
|
117
|
+
|
|
118
|
+
# Group donations by (donor, recipient).
|
|
119
|
+
grouped: dict[tuple[str, str], list[tuple[dt.date, float]]] = defaultdict(list)
|
|
120
|
+
for row in donations:
|
|
121
|
+
donor = row.get(donation_donor_col, "").strip()
|
|
122
|
+
recip = row.get(donation_recipient_col, "").strip()
|
|
123
|
+
d = parse_date(row.get(donation_date_col, ""))
|
|
124
|
+
try:
|
|
125
|
+
amt = float(row.get(donation_amount_col, "0") or 0)
|
|
126
|
+
except ValueError:
|
|
127
|
+
amt = 0.0
|
|
128
|
+
if not (donor and recip and d):
|
|
129
|
+
continue
|
|
130
|
+
grouped[(donor, recip)].append((d, amt))
|
|
131
|
+
|
|
132
|
+
results = []
|
|
133
|
+
skipped = 0
|
|
134
|
+
for (donor, recip), records in grouped.items():
|
|
135
|
+
if len(records) < min_donations:
|
|
136
|
+
skipped += 1
|
|
137
|
+
continue
|
|
138
|
+
# Only test if donor appears in cross-links (when provided). The
|
|
139
|
+
# (donor, candidate) tuple itself is NOT what's in matched_pairs —
|
|
140
|
+
# cross_links pairs are (donor, vendor). We use the cross-link to
|
|
141
|
+
# map donor → vendor name(s) so the vendor-date index resolves.
|
|
142
|
+
if matched_pairs is not None and donor not in donor_to_vendors:
|
|
143
|
+
skipped += 1
|
|
144
|
+
continue
|
|
145
|
+
# Try direct donor→awards first, then go through cross-link vendor names.
|
|
146
|
+
award_dates = list(vendor_to_award_dates.get(donor, []))
|
|
147
|
+
if not award_dates:
|
|
148
|
+
award_dates = list(vendor_to_award_dates.get(recip, []))
|
|
149
|
+
if not award_dates and donor_to_vendors.get(donor):
|
|
150
|
+
for vendor_name in donor_to_vendors[donor]:
|
|
151
|
+
award_dates.extend(vendor_to_award_dates.get(vendor_name, []))
|
|
152
|
+
if not award_dates:
|
|
153
|
+
skipped += 1
|
|
154
|
+
continue
|
|
155
|
+
|
|
156
|
+
donation_dates = [d for (d, _) in records]
|
|
157
|
+
observed = statistics.mean(
|
|
158
|
+
_nearest_distance(d, award_dates) for d in donation_dates
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
permuted_means = [
|
|
162
|
+
_permute(len(award_dates), donation_dates, global_min, global_max, rng)
|
|
163
|
+
for _ in range(n_permutations)
|
|
164
|
+
]
|
|
165
|
+
p_value = sum(1 for m in permuted_means if m <= observed) / n_permutations
|
|
166
|
+
null_mean = statistics.mean(permuted_means)
|
|
167
|
+
null_std = statistics.pstdev(permuted_means) or 1.0
|
|
168
|
+
effect_size = (null_mean - observed) / null_std
|
|
169
|
+
|
|
170
|
+
results.append(
|
|
171
|
+
{
|
|
172
|
+
"donor": donor,
|
|
173
|
+
"recipient": recip,
|
|
174
|
+
"n_donations": len(records),
|
|
175
|
+
"n_award_dates": len(award_dates),
|
|
176
|
+
"observed_mean_days": round(observed, 2),
|
|
177
|
+
"null_mean_days": round(null_mean, 2),
|
|
178
|
+
"p_value": round(p_value, 4),
|
|
179
|
+
"effect_size_sd": round(effect_size, 2),
|
|
180
|
+
"significant": p_value < p_threshold,
|
|
181
|
+
"total_donation_amount": round(sum(a for (_, a) in records), 2),
|
|
182
|
+
}
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
results.sort(key=lambda r: r["p_value"])
|
|
186
|
+
|
|
187
|
+
payload = {
|
|
188
|
+
"metadata": {
|
|
189
|
+
"n_permutations": n_permutations,
|
|
190
|
+
"min_donations": min_donations,
|
|
191
|
+
"p_threshold": p_threshold,
|
|
192
|
+
"seed": seed,
|
|
193
|
+
"n_pairs_tested": len(results),
|
|
194
|
+
"n_pairs_skipped": skipped,
|
|
195
|
+
"n_significant": sum(1 for r in results if r["significant"]),
|
|
196
|
+
"observation_window": [global_min.isoformat(), global_max.isoformat()],
|
|
197
|
+
},
|
|
198
|
+
"results": results,
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
Path(out_path).write_text(json.dumps(payload, indent=2))
|
|
202
|
+
return payload
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def main() -> int:
|
|
206
|
+
p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
207
|
+
p.add_argument("--donations", required=True)
|
|
208
|
+
p.add_argument("--donation-date-col", required=True)
|
|
209
|
+
p.add_argument("--donation-amount-col", required=True)
|
|
210
|
+
p.add_argument("--donation-donor-col", required=True)
|
|
211
|
+
p.add_argument("--donation-recipient-col", required=True)
|
|
212
|
+
p.add_argument("--contracts", required=True)
|
|
213
|
+
p.add_argument("--contract-date-col", required=True)
|
|
214
|
+
p.add_argument("--contract-vendor-col", required=True)
|
|
215
|
+
p.add_argument(
|
|
216
|
+
"--cross-links",
|
|
217
|
+
help="Optional cross_links.csv to restrict (donor, vendor) pairs",
|
|
218
|
+
)
|
|
219
|
+
p.add_argument("--permutations", type=int, default=1000)
|
|
220
|
+
p.add_argument("--min-donations", type=int, default=3)
|
|
221
|
+
p.add_argument("--p-threshold", type=float, default=0.05)
|
|
222
|
+
p.add_argument("--seed", type=int)
|
|
223
|
+
p.add_argument("--out", default="timing.json")
|
|
224
|
+
a = p.parse_args()
|
|
225
|
+
|
|
226
|
+
payload = analyze(
|
|
227
|
+
donations_path=a.donations,
|
|
228
|
+
donation_date_col=a.donation_date_col,
|
|
229
|
+
donation_amount_col=a.donation_amount_col,
|
|
230
|
+
donation_donor_col=a.donation_donor_col,
|
|
231
|
+
donation_recipient_col=a.donation_recipient_col,
|
|
232
|
+
contracts_path=a.contracts,
|
|
233
|
+
contract_date_col=a.contract_date_col,
|
|
234
|
+
contract_vendor_col=a.contract_vendor_col,
|
|
235
|
+
cross_links_path=a.cross_links,
|
|
236
|
+
n_permutations=a.permutations,
|
|
237
|
+
min_donations=a.min_donations,
|
|
238
|
+
p_threshold=a.p_threshold,
|
|
239
|
+
seed=a.seed,
|
|
240
|
+
out_path=a.out,
|
|
241
|
+
)
|
|
242
|
+
meta = payload["metadata"]
|
|
243
|
+
print(
|
|
244
|
+
f"Tested {meta['n_pairs_tested']} pairs ({meta['n_pairs_skipped']} skipped). "
|
|
245
|
+
f"Significant (p<{meta['p_threshold']}): {meta['n_significant']}. "
|
|
246
|
+
f"Wrote {a.out}"
|
|
247
|
+
)
|
|
248
|
+
return 0
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
if __name__ == "__main__":
|
|
252
|
+
raise SystemExit(main())
|
|
@@ -18,10 +18,11 @@ triggers:
|
|
|
18
18
|
- "force push detected"
|
|
19
19
|
- "IOC extraction"
|
|
20
20
|
toolsets:
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
21
|
+
- Bash
|
|
22
|
+
- WebFetch
|
|
23
|
+
- Read
|
|
24
|
+
- Write
|
|
25
|
+
- Agent
|
|
25
26
|
---
|
|
26
27
|
|
|
27
28
|
# OSS Security Forensics Skill
|
|
@@ -43,7 +44,7 @@ Read these before every investigation step. Violating them invalidates the repor
|
|
|
43
44
|
4. **No Evidence Fabrication**: The hypothesis validator MUST mechanically check that every cited evidence ID actually exists in the evidence store before accepting a hypothesis.
|
|
44
45
|
5. **Proof-Required Disproval**: A hypothesis cannot be dismissed without a specific, evidence-backed counter-argument. "No evidence found" is not sufficient to disprove—it only makes a hypothesis inconclusive.
|
|
45
46
|
6. **SHA/URL Double-Verification**: Any commit SHA, URL, or external identifier cited as evidence must be independently confirmed from at least two sources before being marked as verified.
|
|
46
|
-
7. **Suspicious Code Rule**: Never run code found inside the investigated repository locally. Analyze statically only, or use `
|
|
47
|
+
7. **Suspicious Code Rule**: Never run code found inside the investigated repository locally. Analyze statically only, or use `Bash` in a sandboxed environment.
|
|
47
48
|
8. **Secret Redaction**: Any API keys, tokens, or credentials discovered during investigation must be redacted in the final report. Log them internally only.
|
|
48
49
|
|
|
49
50
|
---
|
|
@@ -93,7 +94,7 @@ Read these before every investigation step. Violating them invalidates the repor
|
|
|
93
94
|
- Provided Indicators of Compromise: commit SHAs, file paths, package names, IP addresses, domains, API keys/tokens, malicious URLs
|
|
94
95
|
- Any linked vendor security reports or blog posts
|
|
95
96
|
|
|
96
|
-
**Tools**: Reasoning only, or `
|
|
97
|
+
**Tools**: Reasoning only, or `Bash` for regex extraction from large text blocks.
|
|
97
98
|
|
|
98
99
|
**Output**: Populate `iocs.md` with extracted IOCs. Each IOC must have:
|
|
99
100
|
- Type (from: COMMIT_SHA, FILE_PATH, API_KEY, SECRET, IP_ADDRESS, DOMAIN, PACKAGE_NAME, ACTOR_USERNAME, MALICIOUS_URL, OTHER)
|
|
@@ -106,7 +107,7 @@ Read these before every investigation step. Violating them invalidates the repor
|
|
|
106
107
|
|
|
107
108
|
## Phase 2: Parallel Evidence Collection
|
|
108
109
|
|
|
109
|
-
Spawn up to 5 specialist investigator sub-agents using `
|
|
110
|
+
Spawn up to 5 specialist investigator sub-agents using `Agent` (batch mode, max 3 concurrent). Each investigator has a **single data source** and must not mix sources.
|
|
110
111
|
|
|
111
112
|
> **Orchestrator note**: Pass the IOC list from Phase 1 and the investigation time window in the `context` field of each delegated task.
|
|
112
113
|
|
|
@@ -282,7 +283,7 @@ LIMIT 200
|
|
|
282
283
|
|
|
283
284
|
**Actions**:
|
|
284
285
|
- For each commit SHA: attempt recovery via direct GitHub URL (`github.com/OWNER/REPO/commit/SHA.patch`)
|
|
285
|
-
- For each domain/IP: check passive DNS, WHOIS records (via `
|
|
286
|
+
- For each domain/IP: check passive DNS, WHOIS records (via `WebFetch` on public WHOIS services)
|
|
286
287
|
- For each package name: check npm/PyPI for matching malicious package reports
|
|
287
288
|
- For each actor username: check GitHub profile, contribution history, account age
|
|
288
289
|
- Recover force-pushed commits using 3 methods (see [recovery-techniques.md](./references/recovery-techniques.md))
|
|
@@ -319,7 +320,7 @@ A hypothesis must:
|
|
|
319
320
|
- Typosquatting: near-identical package name targeting misspellers
|
|
320
321
|
- Credential Leak: token/key accidentally committed then force-pushed to erase
|
|
321
322
|
|
|
322
|
-
For each hypothesis, spawn a `
|
|
323
|
+
For each hypothesis, spawn a `Agent` sub-agent to attempt to find disconfirming evidence before confirming.
|
|
323
324
|
|
|
324
325
|
---
|
|
325
326
|
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
OSS Forensics Evidence Store Manager
|
|
4
|
+
Manages a JSON-based evidence store for forensic investigations.
|
|
5
|
+
|
|
6
|
+
Commands:
|
|
7
|
+
add - Add a piece of evidence
|
|
8
|
+
list - List all evidence (optionally filter by type or actor)
|
|
9
|
+
verify - Re-check SHA-256 hashes for integrity
|
|
10
|
+
query - Search evidence by keyword
|
|
11
|
+
export - Export evidence as a Markdown table
|
|
12
|
+
summary - Print investigation statistics
|
|
13
|
+
|
|
14
|
+
Usage example:
|
|
15
|
+
python3 evidence-store.py --store evidence.json add \
|
|
16
|
+
--source "git fsck output" --content "dangling commit abc123" \
|
|
17
|
+
--type git --actor "malicious-user" --url "https://github.com/owner/repo/commit/abc123"
|
|
18
|
+
|
|
19
|
+
python3 evidence-store.py --store evidence.json list --type git
|
|
20
|
+
python3 evidence-store.py --store evidence.json verify
|
|
21
|
+
python3 evidence-store.py --store evidence.json export > evidence-table.md
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import json
|
|
25
|
+
import argparse
|
|
26
|
+
import os
|
|
27
|
+
import datetime
|
|
28
|
+
import hashlib
|
|
29
|
+
import sys
|
|
30
|
+
|
|
31
|
+
EVIDENCE_TYPES = [
|
|
32
|
+
"git", # Local git repository data (commits, reflog, fsck)
|
|
33
|
+
"gh_api", # GitHub REST API responses
|
|
34
|
+
"gh_archive", # GitHub Archive / BigQuery query results
|
|
35
|
+
"web_archive", # Wayback Machine snapshots
|
|
36
|
+
"ioc", # Indicator of Compromise (SHA, domain, IP, package name, etc.)
|
|
37
|
+
"analysis", # Derived analysis / cross-source correlation result
|
|
38
|
+
"manual", # Manually noted observation
|
|
39
|
+
"vendor_report", # External security vendor report excerpt
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
VERIFICATION_STATES = ["unverified", "single_source", "multi_source_verified"]
|
|
43
|
+
|
|
44
|
+
IOC_TYPES = [
|
|
45
|
+
"COMMIT_SHA", "FILE_PATH", "API_KEY", "SECRET", "IP_ADDRESS",
|
|
46
|
+
"DOMAIN", "PACKAGE_NAME", "ACTOR_USERNAME", "MALICIOUS_URL",
|
|
47
|
+
"WORKFLOW_FILE", "BRANCH_NAME", "TAG_NAME", "RELEASE_NAME", "OTHER",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _now_iso():
|
|
52
|
+
return datetime.datetime.now(datetime.timezone.utc).isoformat(timespec="seconds") + "Z"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _sha256(content: str) -> str:
|
|
56
|
+
return hashlib.sha256(content.encode("utf-8")).hexdigest()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class EvidenceStore:
|
|
60
|
+
def __init__(self, filepath: str):
|
|
61
|
+
self.filepath = filepath
|
|
62
|
+
self.data = {
|
|
63
|
+
"metadata": {
|
|
64
|
+
"version": "2.0",
|
|
65
|
+
"created_at": _now_iso(),
|
|
66
|
+
"last_updated": _now_iso(),
|
|
67
|
+
"investigation": "",
|
|
68
|
+
"target_repo": "",
|
|
69
|
+
},
|
|
70
|
+
"evidence": [],
|
|
71
|
+
"chain_of_custody": [],
|
|
72
|
+
}
|
|
73
|
+
if os.path.exists(filepath):
|
|
74
|
+
try:
|
|
75
|
+
with open(filepath, "r", encoding="utf-8") as f:
|
|
76
|
+
self.data = json.load(f)
|
|
77
|
+
except (json.JSONDecodeError, IOError) as e:
|
|
78
|
+
print(f"Error loading evidence store '{filepath}': {e}", file=sys.stderr)
|
|
79
|
+
print("Hint: The file might be corrupted. Check for manual edits or syntax errors.", file=sys.stderr)
|
|
80
|
+
sys.exit(1)
|
|
81
|
+
|
|
82
|
+
def _save(self):
|
|
83
|
+
self.data["metadata"]["last_updated"] = _now_iso()
|
|
84
|
+
with open(self.filepath, "w", encoding="utf-8") as f:
|
|
85
|
+
json.dump(self.data, f, indent=2, ensure_ascii=False)
|
|
86
|
+
|
|
87
|
+
def _next_id(self) -> str:
|
|
88
|
+
return f"EV-{len(self.data['evidence']) + 1:04d}"
|
|
89
|
+
|
|
90
|
+
def add(
|
|
91
|
+
self,
|
|
92
|
+
source: str,
|
|
93
|
+
content: str,
|
|
94
|
+
evidence_type: str,
|
|
95
|
+
actor: str = None,
|
|
96
|
+
url: str = None,
|
|
97
|
+
timestamp: str = None,
|
|
98
|
+
ioc_type: str = None,
|
|
99
|
+
verification: str = "unverified",
|
|
100
|
+
notes: str = None,
|
|
101
|
+
) -> str:
|
|
102
|
+
evidence_id = self._next_id()
|
|
103
|
+
entry = {
|
|
104
|
+
"id": evidence_id,
|
|
105
|
+
"type": evidence_type,
|
|
106
|
+
"source": source,
|
|
107
|
+
"content": content,
|
|
108
|
+
"content_sha256": _sha256(content),
|
|
109
|
+
"actor": actor,
|
|
110
|
+
"url": url,
|
|
111
|
+
"event_timestamp": timestamp,
|
|
112
|
+
"collected_at": _now_iso(),
|
|
113
|
+
"ioc_type": ioc_type,
|
|
114
|
+
"verification": verification,
|
|
115
|
+
"notes": notes,
|
|
116
|
+
}
|
|
117
|
+
self.data["evidence"].append(entry)
|
|
118
|
+
self.data["chain_of_custody"].append({
|
|
119
|
+
"action": "add",
|
|
120
|
+
"evidence_id": evidence_id,
|
|
121
|
+
"timestamp": _now_iso(),
|
|
122
|
+
"source": source,
|
|
123
|
+
})
|
|
124
|
+
self._save()
|
|
125
|
+
return evidence_id
|
|
126
|
+
|
|
127
|
+
def list_evidence(self, filter_type: str = None, filter_actor: str = None):
|
|
128
|
+
results = self.data["evidence"]
|
|
129
|
+
if filter_type:
|
|
130
|
+
results = [e for e in results if e.get("type") == filter_type]
|
|
131
|
+
if filter_actor:
|
|
132
|
+
results = [e for e in results if e.get("actor") == filter_actor]
|
|
133
|
+
return results
|
|
134
|
+
|
|
135
|
+
def verify_integrity(self):
|
|
136
|
+
"""Re-compute SHA-256 for all entries and report mismatches."""
|
|
137
|
+
issues = []
|
|
138
|
+
for entry in self.data["evidence"]:
|
|
139
|
+
expected = _sha256(entry["content"])
|
|
140
|
+
stored = entry.get("content_sha256", "")
|
|
141
|
+
if expected != stored:
|
|
142
|
+
issues.append({
|
|
143
|
+
"id": entry["id"],
|
|
144
|
+
"stored_sha256": stored,
|
|
145
|
+
"computed_sha256": expected,
|
|
146
|
+
})
|
|
147
|
+
return issues
|
|
148
|
+
|
|
149
|
+
def query(self, keyword: str):
|
|
150
|
+
"""Search for keyword in content, source, actor, or url."""
|
|
151
|
+
keyword_lower = keyword.lower()
|
|
152
|
+
return [
|
|
153
|
+
e for e in self.data["evidence"]
|
|
154
|
+
if keyword_lower in (e.get("content", "") or "").lower()
|
|
155
|
+
or keyword_lower in (e.get("source", "") or "").lower()
|
|
156
|
+
or keyword_lower in (e.get("actor", "") or "").lower()
|
|
157
|
+
or keyword_lower in (e.get("url", "") or "").lower()
|
|
158
|
+
]
|
|
159
|
+
|
|
160
|
+
def export_markdown(self) -> str:
|
|
161
|
+
lines = [
|
|
162
|
+
"# Evidence Registry",
|
|
163
|
+
"",
|
|
164
|
+
f"**Store**: `{self.filepath}`",
|
|
165
|
+
f"**Last Updated**: {self.data['metadata'].get('last_updated', 'N/A')}",
|
|
166
|
+
f"**Total Evidence Items**: {len(self.data['evidence'])}",
|
|
167
|
+
"",
|
|
168
|
+
"| ID | Type | Source | Actor | Verification | Event Timestamp | URL |",
|
|
169
|
+
"|----|------|--------|-------|--------------|-----------------|-----|",
|
|
170
|
+
]
|
|
171
|
+
for e in self.data["evidence"]:
|
|
172
|
+
url = e.get("url") or ""
|
|
173
|
+
url_display = f"[link]({url})" if url else ""
|
|
174
|
+
lines.append(
|
|
175
|
+
f"| {e['id']} | {e.get('type','')} | {e.get('source','')} "
|
|
176
|
+
f"| {e.get('actor') or ''} | {e.get('verification','')} "
|
|
177
|
+
f"| {e.get('event_timestamp') or ''} | {url_display} |"
|
|
178
|
+
)
|
|
179
|
+
lines.append("")
|
|
180
|
+
lines.append("## Chain of Custody")
|
|
181
|
+
lines.append("")
|
|
182
|
+
lines.append("| Evidence ID | Action | Timestamp | Source |")
|
|
183
|
+
lines.append("|-------------|--------|-----------|--------|")
|
|
184
|
+
for c in self.data["chain_of_custody"]:
|
|
185
|
+
lines.append(
|
|
186
|
+
f"| {c.get('evidence_id','')} | {c.get('action','')} "
|
|
187
|
+
f"| {c.get('timestamp','')} | {c.get('source','')} |"
|
|
188
|
+
)
|
|
189
|
+
return "\n".join(lines)
|
|
190
|
+
|
|
191
|
+
def summary(self) -> dict:
|
|
192
|
+
by_type = {}
|
|
193
|
+
by_verification = {}
|
|
194
|
+
actors = set()
|
|
195
|
+
for e in self.data["evidence"]:
|
|
196
|
+
t = e.get("type", "unknown")
|
|
197
|
+
by_type[t] = by_type.get(t, 0) + 1
|
|
198
|
+
v = e.get("verification", "unverified")
|
|
199
|
+
by_verification[v] = by_verification.get(v, 0) + 1
|
|
200
|
+
if e.get("actor"):
|
|
201
|
+
actors.add(e["actor"])
|
|
202
|
+
return {
|
|
203
|
+
"total": len(self.data["evidence"]),
|
|
204
|
+
"by_type": by_type,
|
|
205
|
+
"by_verification": by_verification,
|
|
206
|
+
"unique_actors": sorted(actors),
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def main():
|
|
211
|
+
parser = argparse.ArgumentParser(
|
|
212
|
+
description="OSS Forensics Evidence Store Manager v2.0",
|
|
213
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
214
|
+
)
|
|
215
|
+
parser.add_argument("--store", default="evidence.json", help="Path to evidence JSON file (default: evidence.json)")
|
|
216
|
+
|
|
217
|
+
subparsers = parser.add_subparsers(dest="command", metavar="COMMAND")
|
|
218
|
+
|
|
219
|
+
# --- add ---
|
|
220
|
+
add_p = subparsers.add_parser("add", help="Add a new evidence entry")
|
|
221
|
+
add_p.add_argument("--source", required=True, help="Where this evidence came from (e.g. 'git fsck', 'GH API /commits')")
|
|
222
|
+
add_p.add_argument("--content", required=True, help="The evidence content (commit SHA, API response excerpt, etc.)")
|
|
223
|
+
add_p.add_argument("--type", required=True, choices=EVIDENCE_TYPES, dest="evidence_type", help="Evidence type")
|
|
224
|
+
add_p.add_argument("--actor", help="GitHub handle or email of associated actor")
|
|
225
|
+
add_p.add_argument("--url", help="URL to original source")
|
|
226
|
+
add_p.add_argument("--timestamp", help="When the event occurred (ISO 8601)")
|
|
227
|
+
add_p.add_argument("--ioc-type", choices=IOC_TYPES, help="IOC subtype (for --type ioc)")
|
|
228
|
+
add_p.add_argument("--verification", choices=VERIFICATION_STATES, default="unverified")
|
|
229
|
+
add_p.add_argument("--notes", help="Additional investigator notes")
|
|
230
|
+
add_p.add_argument("--quiet", action="store_true", help="Suppress success message")
|
|
231
|
+
|
|
232
|
+
# --- list ---
|
|
233
|
+
list_p = subparsers.add_parser("list", help="List all evidence entries")
|
|
234
|
+
list_p.add_argument("--type", dest="filter_type", choices=EVIDENCE_TYPES, help="Filter by type")
|
|
235
|
+
list_p.add_argument("--actor", dest="filter_actor", help="Filter by actor")
|
|
236
|
+
|
|
237
|
+
# --- verify ---
|
|
238
|
+
subparsers.add_parser("verify", help="Verify SHA-256 integrity of all evidence content")
|
|
239
|
+
|
|
240
|
+
# --- query ---
|
|
241
|
+
query_p = subparsers.add_parser("query", help="Search evidence by keyword")
|
|
242
|
+
query_p.add_argument("keyword", help="Keyword to search for")
|
|
243
|
+
|
|
244
|
+
# --- export ---
|
|
245
|
+
subparsers.add_parser("export", help="Export evidence as a Markdown table (stdout)")
|
|
246
|
+
|
|
247
|
+
# --- summary ---
|
|
248
|
+
subparsers.add_parser("summary", help="Print investigation statistics")
|
|
249
|
+
|
|
250
|
+
args = parser.parse_args()
|
|
251
|
+
|
|
252
|
+
if not args.command:
|
|
253
|
+
parser.print_help()
|
|
254
|
+
sys.exit(0)
|
|
255
|
+
|
|
256
|
+
store = EvidenceStore(args.store)
|
|
257
|
+
|
|
258
|
+
if args.command == "add":
|
|
259
|
+
eid = store.add(
|
|
260
|
+
source=args.source,
|
|
261
|
+
content=args.content,
|
|
262
|
+
evidence_type=args.evidence_type,
|
|
263
|
+
actor=args.actor,
|
|
264
|
+
url=args.url,
|
|
265
|
+
timestamp=args.timestamp,
|
|
266
|
+
ioc_type=args.ioc_type,
|
|
267
|
+
verification=args.verification,
|
|
268
|
+
notes=args.notes,
|
|
269
|
+
)
|
|
270
|
+
if not getattr(args, "quiet", False):
|
|
271
|
+
print(f"✓ Added evidence: {eid}")
|
|
272
|
+
|
|
273
|
+
elif args.command == "list":
|
|
274
|
+
items = store.list_evidence(
|
|
275
|
+
filter_type=getattr(args, "filter_type", None),
|
|
276
|
+
filter_actor=getattr(args, "filter_actor", None),
|
|
277
|
+
)
|
|
278
|
+
if not items:
|
|
279
|
+
print("No evidence found.")
|
|
280
|
+
for e in items:
|
|
281
|
+
actor_str = f" | actor: {e['actor']}" if e.get("actor") else ""
|
|
282
|
+
url_str = f" | {e['url']}" if e.get("url") else ""
|
|
283
|
+
print(f"[{e['id']}] {e['type']:12s} | {e['verification']:20s} | {e['source']}{actor_str}{url_str}")
|
|
284
|
+
|
|
285
|
+
elif args.command == "verify":
|
|
286
|
+
issues = store.verify_integrity()
|
|
287
|
+
if not issues:
|
|
288
|
+
print(f"✓ All {len(store.data['evidence'])} evidence entries passed SHA-256 integrity check.")
|
|
289
|
+
else:
|
|
290
|
+
print(f"✗ {len(issues)} integrity issue(s) detected:")
|
|
291
|
+
for i in issues:
|
|
292
|
+
print(f" [{i['id']}] stored={i['stored_sha256'][:16]}... computed={i['computed_sha256'][:16]}...")
|
|
293
|
+
sys.exit(1)
|
|
294
|
+
|
|
295
|
+
elif args.command == "query":
|
|
296
|
+
results = store.query(args.keyword)
|
|
297
|
+
print(f"Found {len(results)} result(s) for '{args.keyword}':")
|
|
298
|
+
for e in results:
|
|
299
|
+
print(f" [{e['id']}] {e['type']} | {e['source']} | {e['content'][:80]}")
|
|
300
|
+
|
|
301
|
+
elif args.command == "export":
|
|
302
|
+
print(store.export_markdown())
|
|
303
|
+
|
|
304
|
+
elif args.command == "summary":
|
|
305
|
+
s = store.summary()
|
|
306
|
+
print(f"Total evidence items : {s['total']}")
|
|
307
|
+
print(f"By type : {json.dumps(s['by_type'], indent=2)}")
|
|
308
|
+
print(f"By verification : {json.dumps(s['by_verification'], indent=2)}")
|
|
309
|
+
print(f"Unique actors : {s['unique_actors']}")
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
if __name__ == "__main__":
|
|
313
|
+
main()
|
|
@@ -12,7 +12,7 @@ This is an optional third-party workflow, not a a core capability.
|
|
|
12
12
|
|
|
13
13
|
Important expectations:
|
|
14
14
|
- Parallel is a paid service with a free tier, not a fully free local tool.
|
|
15
|
-
- It overlaps with native `
|
|
15
|
+
- It overlaps with native `WebSearch` / `WebFetch`, so do not prefer it by default for ordinary lookups.
|
|
16
16
|
- Prefer this skill when the user mentions Parallel specifically or needs capabilities like Parallel's enrichment, FindAll, or monitor workflows.
|
|
17
17
|
|
|
18
18
|
`parallel-cli` is designed for agents:
|
|
@@ -30,7 +30,7 @@ Prefer this skill when:
|
|
|
30
30
|
- You need async deep research jobs that can be launched and polled later
|
|
31
31
|
- You need structured enrichment, FindAll entity discovery, or monitoring
|
|
32
32
|
|
|
33
|
-
Prefer native `
|
|
33
|
+
Prefer native `WebSearch` / `WebFetch` for quick one-off lookups when Parallel is not specifically requested.
|
|
34
34
|
|
|
35
35
|
## Installation
|
|
36
36
|
|
|
@@ -63,7 +63,7 @@ ssh -p 443 -o StrictHostKeyChecking=no -R0:localhost:8000 "$PINGGY_TOKEN+a.pingg
|
|
|
63
63
|
|
|
64
64
|
## Procedure — Start a Tunnel and Get the URL
|
|
65
65
|
|
|
66
|
-
The model SHOULD use the `
|
|
66
|
+
The model SHOULD use the `Bash` tool. The tunnel must stay alive for the duration of the share, so run it as a background process and parse the public URL from stdout.
|
|
67
67
|
|
|
68
68
|
### 1. Confirm a local origin is up
|
|
69
69
|
|
|
@@ -76,7 +76,7 @@ If nothing is listening yet, start it first (e.g. `python3 -m http.server 8000 -
|
|
|
76
76
|
|
|
77
77
|
### 2. Launch the tunnel as a background process
|
|
78
78
|
|
|
79
|
-
Use `
|
|
79
|
+
Use `Bash(background=True)` and capture output to a logfile (Pinggy prints the URLs on stdout, then keeps the connection open):
|
|
80
80
|
|
|
81
81
|
```bash
|
|
82
82
|
LOG=/tmp/pinggy-8000.log
|
|
@@ -127,7 +127,7 @@ kill "$(cat /tmp/pinggy-8000.pid)"
|
|
|
127
127
|
pkill -f 'ssh -p 443 .* free@a\.pinggy\.io'
|
|
128
128
|
```
|
|
129
129
|
|
|
130
|
-
If you have a session_id from `
|
|
130
|
+
If you have a session_id from `Bash(background=True)`, prefer `process(action='kill', session_id=...)`.
|
|
131
131
|
|
|
132
132
|
## Access Control via Username Keywords
|
|
133
133
|
|