mirror of
https://github.com/BaaRai/ARTEX.git
synced 2026-11-13 11:40:53 +08:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b81c6238a7 |
@@ -1,24 +0,0 @@
|
||||
package agent
|
||||
|
||||
import "strings"
|
||||
import "testing"
|
||||
|
||||
// The operator-handoff discipline must stay in the executor agents' default
|
||||
// seeds: environmental failures stop the retry loop and become one
|
||||
// copy-pastable ask_operator command.
|
||||
func TestPromptSeedsCarryOperatorHandoff(t *testing.T) {
|
||||
for name, tmpl := range BuiltinPromptSeeds() {
|
||||
switch name {
|
||||
case "worker", "pentest":
|
||||
for _, marker := range []string{
|
||||
"Operator handoff on environmental blocks",
|
||||
"one line, copy-pastable",
|
||||
"never batch",
|
||||
} {
|
||||
if !strings.Contains(tmpl, marker) {
|
||||
t.Errorf("%s seed missing operator-handoff marker %q", name, marker)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,11 +90,7 @@ Finding quality gates (proof standard, measured impact, testing bounds):
|
||||
- **Every reported finding carries a measured impact**: the attacker outcome as a number with its denominator ("3 of 20 payload variants returned other-tenant rows; 17 rejected", "commands executed: 2, output returned: yes") plus what the defense blocked. "The attacker can dump the table" is a claim; "3 of 20 payloads returned rows" is a measurement. If no number is obtainable, record what was attempted and why it is not measurable.
|
||||
- **State the proof basis** on every finding: executed-live, executed-lab, or static-analysis-unexecuted. An executed proof outranks a static conclusion; anything never actually triggered is "potential/suspect", never confirmed.
|
||||
- **Testing bounds** (bounded and non-destructive does NOT mean read-only): state-changing requests are in bounds when they are the finding's mechanism. Prove the mechanism on your own objects/accounts first, then confirm cross-entity reach on at most three non-owned objects with the smallest request that demonstrates the boundary; prefer reversible values, restore originals where possible, and record every touched object ID in the evidence.
|
||||
- **Priority discipline**: low-impact oracles (user/account enumeration, account-existence checks, response-timing differences) and low-yield misconfigurations (permissive CORS) are not standalone findings; record them only when they directly chain into account takeover, authentication bypass, or mass data exposure.
|
||||
|
||||
Operator handoff on environmental blocks:
|
||||
- When a command or tool fails for an environmental reason (a sandboxed or filtered network path, a missing binary or key, TLS or proxy interception, a permission this runtime lacks), stop after the first failure: do not retry with different flags, do not switch tools hunting for a bypass, and do not fall through to an equivalent command.
|
||||
- Call ask_operator with the exact single command the operator should run in their own terminal: one line, copy-pastable, followed by what to paste back. One command per request; never batch. Wait for the reply instead of burning turns.`
|
||||
- **Priority discipline**: low-impact oracles (user/account enumeration, account-existence checks, response-timing differences) and low-yield misconfigurations (permissive CORS) are not standalone findings; record them only when they directly chain into account takeover, authentication bypass, or mass data exposure.`
|
||||
|
||||
// DefaultAssistantPrompt is the starter/fallback body for CUSTOM conversational
|
||||
// agents — they have no per-key in-code default. It is seeded into agent_prompts
|
||||
|
||||
+1
-5
@@ -262,11 +262,7 @@ Finding quality gates (proof standard, measured impact, testing bounds):
|
||||
- **Every reported finding carries a measured impact**: the attacker outcome as a number with its denominator ("3 of 20 payload variants returned other-tenant rows; 17 rejected", "commands executed: 2, output returned: yes") plus what the defense blocked. "The attacker can dump the table" is a claim; "3 of 20 payloads returned rows" is a measurement. If no number is obtainable, record what was attempted and why it is not measurable.
|
||||
- **State the proof basis** on every finding: executed-live, executed-lab, or static-analysis-unexecuted. An executed proof outranks a static conclusion; anything never actually triggered is "potential/suspect", never confirmed.
|
||||
- **Testing bounds** (bounded and non-destructive does NOT mean read-only): state-changing requests are in bounds when they are the finding's mechanism. Prove the mechanism on your own objects/accounts first, then confirm cross-entity reach on at most three non-owned objects with the smallest request that demonstrates the boundary; prefer reversible values, restore originals where possible, and record every touched object ID in the evidence.
|
||||
- **Priority discipline**: low-impact oracles (user/account enumeration, account-existence checks, response-timing differences) and low-yield misconfigurations (permissive CORS) are not standalone findings; record them only when they directly chain into account takeover, authentication bypass, or mass data exposure.
|
||||
|
||||
Operator handoff on environmental blocks:
|
||||
- When a command or tool fails for an environmental reason (a sandboxed or filtered network path, a missing binary or key, TLS or proxy interception, a permission this runtime lacks), stop after the first failure: do not retry with different flags, do not switch tools hunting for a bypass, and do not fall through to an equivalent command.
|
||||
- Call ask_operator with the exact single command the operator should run in their own terminal: one line, copy-pastable, followed by what to paste back. One command per request; never batch. Wait for the reply instead of burning turns.`
|
||||
- **Priority discipline**: low-impact oracles (user/account enumeration, account-existence checks, response-timing differences) and low-yield misconfigurations (permissive CORS) are not standalone findings; record them only when they directly chain into account takeover, authentication bypass, or mass data exposure.`
|
||||
|
||||
// workerTrafficBlock is section [B]: the traffic-tool note, code-injected only when
|
||||
// traffic capture (recording) is on — i.e. the traffic_* tools actually exist.
|
||||
|
||||
@@ -25,13 +25,13 @@ var askOperatorImageExt = map[string]bool{".png": true, ".jpg": true, ".jpeg": t
|
||||
// resumes the run with the answer as the next input (#41).
|
||||
func (s *Server) toolAskOperator() actool.CoreTool {
|
||||
return wrTool("ask_operator",
|
||||
"Ask the human operator for input you cannot obtain yourself (a captcha solution, an email/SMS OTP code, a 2FA answer, a decision, or an action only they can perform). The question appears in this intent's chat for the operator, optionally with a screenshot, and this intent then pauses; the operator's reply resumes this run with the answer as your next input. Ask precisely, and only when you are genuinely blocked. When the block is environmental (a sandboxed or filtered network path, a missing binary or key, a proxy or TLS failure this runtime cannot fix), do not retry or work around it: include in the question the exact single command the operator should run in their own terminal, as one copy-pastable line, plus what to paste back, then wait.",
|
||||
"Ask the human operator for input you cannot obtain yourself (a captcha solution, an email/SMS OTP code, a 2FA answer, a decision). The question appears in this intent's chat for the operator, optionally with a screenshot, and this intent then pauses; the operator's reply resumes this run with the answer as your next input. Ask precisely, and only when you are genuinely blocked.",
|
||||
map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"question": map[string]any{
|
||||
"type": "string",
|
||||
"description": "What you need from the operator, precisely (e.g. 'enter the 6-digit code sent to x@y', 'solve the captcha in the attached screenshot'). For environmental blocks: the exact single command they should run in their own terminal (one line, copy-pastable) and what to paste back.",
|
||||
"description": "What you need from the operator, precisely (e.g. 'enter the 6-digit code sent to x@y', 'solve the captcha in the attached screenshot').",
|
||||
},
|
||||
"image_path": map[string]any{
|
||||
"type": "string",
|
||||
|
||||
@@ -68,15 +68,3 @@ func TestAskOperatorActivityShape(t *testing.T) {
|
||||
t.Fatalf("unexpected detail payload: %+v", parsed)
|
||||
}
|
||||
}
|
||||
|
||||
// The tool description must tell agents what to do on environmental blocks:
|
||||
// hand the operator one copy-pastable command instead of retrying or hunting
|
||||
// for bypasses. Pattern adapted from bug-bounty-hunting-prompts (MIT).
|
||||
func TestAskOperatorEnvHandoffGuidance(t *testing.T) {
|
||||
desc := (&Server{}).toolAskOperator().Description()
|
||||
for _, marker := range []string{"copy-pastable", "do not retry or work around"} {
|
||||
if !strings.Contains(desc, marker) {
|
||||
t.Errorf("ask_operator description missing env-handoff marker %q", marker)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/Autumn-27/norma/skill"
|
||||
)
|
||||
|
||||
// Every skill shipped in the repository's skills/ directory must parse cleanly
|
||||
// with the real frontmatter parser and carry a description: a broken YAML
|
||||
// scalar silently empties the metadata and the skill stops being discoverable
|
||||
// by the agents (see the skills-YAML fix for the same failure mode).
|
||||
func TestShippedSkillsParseCleanly(t *testing.T) {
|
||||
dir := filepath.Join("..", "skills")
|
||||
entries, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
t.Skipf("no shipped skills dir: %v", err)
|
||||
}
|
||||
reg, err := skill.LoadDir(dir)
|
||||
if err != nil {
|
||||
t.Fatalf("LoadDir on shipped skills: %v", err)
|
||||
}
|
||||
byDir := map[string]skill.Skill{}
|
||||
if reg != nil {
|
||||
for _, sk := range reg.List() {
|
||||
if sk.Dir != "" {
|
||||
byDir[filepath.Base(sk.Dir)] = sk
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, e := range entries {
|
||||
if !e.IsDir() {
|
||||
continue
|
||||
}
|
||||
sk, ok := byDir[e.Name()]
|
||||
if !ok {
|
||||
t.Errorf("skill %s: not parsed by LoadDir (frontmatter broken?)", e.Name())
|
||||
continue
|
||||
}
|
||||
if sk.Description == "" {
|
||||
t.Errorf("skill %s: empty description after parse", e.Name())
|
||||
}
|
||||
}
|
||||
if _, ok := byDir["codeaudit"]; !ok {
|
||||
t.Error("codeaudit skill missing from shipped skills")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
---
|
||||
name: codeaudit
|
||||
description: "Invoke this skill when the task is a white-box audit of source code the operator provided or fetched (a plugin, an application, a repository). Attacker-first forward analysis from every user-controllable input to dangerous sinks, adversarial verification, PoC with measured impact."
|
||||
---
|
||||
|
||||
# Code Audit (white-box source review)
|
||||
|
||||
Under an **authorized** engagement, audit first-party production source code the way an attacker reads it: enumerate every user-controllable input, trace each one forward to dangerous sinks, keep only findings that pass the gates, and prove each one with a PoC and a measured impact. This skill is the source-audit counterpart of live testing; the standing finding quality gates and the adversarial gate in your system prompt apply throughout.
|
||||
|
||||
## Scope rules (production code only)
|
||||
|
||||
Never report findings in, and never trace through: test code, build and packaging scripts, vendored or third-party code, generated code, documentation. If a data flow passes through vendored code, note the dependency boundary and focus on the first-party caller.
|
||||
|
||||
**Exception**: security-relevant infrastructure configuration checked into the repository IS in scope when it affects the application's security assumptions (reverse proxy and nginx directives, trust proxy settings, header forwarding, TLS termination, CORS policy). A directive that promotes a normally trusted value to attacker-controllable is a deployed vulnerability, not an operational note.
|
||||
|
||||
## Phase 1: input inventory and partitioning
|
||||
|
||||
Enumerate **every** user-controllable input: HTTP routes and their parameters, headers, cookies, request bodies, REST and RPC methods, queue and event consumers, CLI arguments, configuration and environment values read at request time, file uploads, import and export endpoints, webhooks and callbacks, scheduled jobs with externally influenced inputs. For each entry point record the handler file:line.
|
||||
|
||||
Partition a large codebase by module or subgraph and hunt each partition separately, but first build the **shared defense catalog**: middleware, auth wrappers, sanitizers, ORM base classes, framework validators. Every trace agent only sees its partition; Phase 2 verification always searches the whole codebase for these shared defenses.
|
||||
|
||||
Write the inventory to the work dir as an artifact. Every input must end the audit with an explicit disposition (traced to a sink, tested and safe, eliminated with evidence). Candidates are hypotheses, not findings; the inventory is the completeness artifact that proves nothing was silently dropped.
|
||||
|
||||
## Phase 2: forward hunt, per vulnerability class
|
||||
|
||||
For each input, trace **forward** (entry point to sink), one vuln class at a time (injection, navigation and XSS, business logic and authorization, file operations, deserialization, cryptography, SSRF). Tool-first order per input:
|
||||
|
||||
1. Read the entry point handler; list every use of the input variable (assignments, arguments, interpolation, concatenation).
|
||||
2. Trace forward function by function until a sink, a sanitizer, or the codebase boundary.
|
||||
- **Never stop at an abstraction boundary**: a router, dispatcher, middleware chain, data fetcher, strategy selector, or callback is a fork into multiple traces, each followed to its conclusion.
|
||||
- **Audit all parameters at every outbound call site** (HTTP client, DB query, message publish): not just the traced input. A validated scoping parameter (tenant, user, account id) that is substituted, dropped, defaulted, or replaced by a wildcard ("~", "*", -1, "all") between validation and the call is a candidate authorization bypass, high severity.
|
||||
- **Exhaust all paths**: an input used in three places is three traces; a safe path never clears the other paths.
|
||||
- **Early-return guards**: a guard protects only what it actually validates; "not null" protects nothing against injection. Verify the check matches the sink context.
|
||||
- **Follow through stores**: input written to DB, cache, or queue continues wherever a reader consumes it.
|
||||
- **Follow through responses**: when input controls the scheme, host, or port of an outbound request, the response body is attacker-influenced (DOM XSS, open redirect downstream); when input selects a resource, enumerate the response mapper fields for sensitive data reaching the caller (CWE-200 candidates).
|
||||
- **Transitive caller search on every candidate sink**: grep all callers of the sink, then callers of callers, until entry points or exhaustion; every additional production path is a potential additional finding.
|
||||
3. Read the actual sink source before confirming anything; search output navigates, source read judges.
|
||||
|
||||
## Gates (a candidate must pass all three)
|
||||
|
||||
1. **Reachable** in production (route registration is not reachability; dev-only guards in the handler's own source remove reachability).
|
||||
2. **Attacker-controlled** data reaches the sink (a value only the server writes is not, unless all its write paths are attacker-influenced).
|
||||
3. **New capability** for the attacker (a privilege they lack, another user's data, code execution, or a state change beyond their rights).
|
||||
|
||||
A candidate failing any gate is a hardening note, never a finding. Comment and naming prose ("by design", "sanitized downstream", "internal only") satisfies no gate; only read code does. A sanitizer whose scope does not match the sink context (an HTML encoder guarding a URL, URL-encoding guarding SQL) is itself a finding.
|
||||
|
||||
## Verification (beyond the standing adversarial gate)
|
||||
|
||||
- Search the **whole** codebase for shared defenses the partition hunt could not see, and verify each defense actually blocks the attack by reading it.
|
||||
- An elimination cites the file:line of the defense, type constraint, or non-attacker origin that kills it. No citation, no elimination.
|
||||
- Do not merge candidates: different entry points, parameters, or attacker privilege levels are separate findings even in one chain.
|
||||
- A sink is only cleared when **all** its call sites are checked; a property is only server-controlled when **all** its writers are.
|
||||
|
||||
## PoC and measured impact
|
||||
|
||||
Prefer executing the PoC against a real instance of the component: deploy it locally (work dir compose, a lab instance, a local CMS install for a plugin) and demonstrate the attacker's goal achieved end to end. Executed beats static.
|
||||
|
||||
When execution is genuinely impossible, a static trace PoC must contain: the concrete attacker input value at the entry, file:line at every propagation step, the literal dangerous value that reaches the sink, and the concrete impact. Label it proof basis static-analysis-unexecuted and mark the finding potential, not confirmed.
|
||||
|
||||
Both cases carry a measured impact with its denominator, per the standing gates.
|
||||
|
||||
## Sweep and count cross-check
|
||||
|
||||
For each confirmed root cause, grep the whole codebase for the same pattern; every instance gets its own disposition (confirmed as its own finding, or eliminated with cited evidence). Before writing the final summary, cross-check: every candidate from Phase 2 appears exactly once as confirmed, eliminated, or downgraded; every confirmed finding with a passing PoC becomes its own report_finding. Silent drops are audit failures.
|
||||
|
||||
## Recording
|
||||
|
||||
- Inventory, dispositions, and the sweep table land as artifacts in the task work dir; key conclusions become facts so later intents inherit them.
|
||||
- Confirmed findings go through report_finding, each with its PoC, measured impact, and proof basis.
|
||||
- Zero confirmed findings is a valid outcome: deliver the coverage statement (which classes were hunted, by which method, across which surfaces, with what result).
|
||||
|
||||
---
|
||||
|
||||
Methodology adapted from VulnHunter (Capital One, Apache-2.0, maintained fork by Neal Bridges) and bug-bounty-hunting-prompts (MIT).
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
|
||||
name: scopesentry-mcp
|
||||
description: Manage the security scanning platform (projects, tasks, templates, assets, nodes) through the ScopeSentry MCP. Use when the user mentions ScopeSentry, MCP, API Key, scan tasks, or asset queries.
|
||||
name: scopesentry
|
||||
description: "Manage the security scanning platform (projects, tasks, templates, assets, nodes) through the ScopeSentry MCP. Invoke this skill when the task needs ScopeSentry operations: creating scans, querying assets, or reading scan results."
|
||||
---
|
||||
|
||||
# ScopeSentry MCP usage guide
|
||||
|
||||
|
||||
Reference in New Issue
Block a user