Nothing today makes an agent check for an existing finding before
registering a new one, and the reporter has no duplicate or collapsing
rules. Duplicates burn reviewer trust on submission (programs read them
as noise or padding) and they inflate the task's apparent progress.
Changes:
- worker and pentest seeds: before report_finding, check
list_findings / list_task_findings for an existing finding with the
same root cause, endpoint, and parameter; the same defect again means
extending that finding's evidence, not registering a duplicate. A
genuinely distinct entry point, parameter, or privilege level remains
its own finding (the adversarial gate's anti-merge rule still holds:
the dedup check collapses only true duplicates).
- reporter seed: an explicit duplicate pass over list_task_findings in
the work steps, plus two discipline rules (no duplicate reports for an
already-reported defect; one finding per report, additional locations
of the same root cause listed inside it, never silently merged or spun
off).
- agent/finding_dedup_seed_test.go guards the markers.
Model benchmarks measure recognition on snippets. Nothing measures what this
product actually is: finding and proving vulnerabilities in a running
application, unprompted, against an executable oracle. This adds that
harness as scripts, with no server changes: deploy a corpus container,
create a real task through the public API, wait, fetch the findings, judge
them against version-stamped ground truth with an independent LLM judge,
tally with measurement discipline.
scripts/benchmark/run_bench.py plus ground_truth/juice-shop.json (8 stable
entries: SQLi login bypass, admin section access, basket IDOR, /ftp
exposure, forged feedback, error disclosure, token tampering, XSS) and a
README documenting the standing rules:
- multi-gate scoring (component 1 / class 2 / keywords 1 / proof 1),
partial credit, gate pattern over aggregate
- findings beyond ground truth are discovery candidates; precision is
reported as bounds until verified
- the judge must not be the model under test (refuses to start, override
knowingly), and must pass a falsification battery before grading
- invalid findings (errored or empty) are excluded from every rate and
named, never counted as misses
- every rate carries its denominator, the aggregate a Wilson 95% interval;
results are stamped with method + ground-truth versions
The task goal bakes in the standing quality gates, so the benchmark
measures the product as it behaves, prompt rules included.
Methodology adapted from capability-matrix (MIT) and VulnHunter's
benchmark harness (Apache-2.0).
When a worker hits an environmental failure (sandboxed or filtered
network path, missing binary or key, TLS or proxy interception, missing
runtime permission), nothing today tells it how to stop: transcripts
show turn after turn of retries with different flags, equivalent tools,
or fallback transports, all burning budget against a wall the agent
cannot move.
Changes:
- the ask_operator tool description now covers environmental blocks
explicitly: do not retry or work around; include the exact single
command the operator should run in their own terminal as one
copy-pastable line, plus what to paste back, then wait. The question
parameter description gains the same guidance.
- worker and pentest seeds gain an "Operator handoff on environmental
blocks" block: stop after the first environmental failure, no
flag-hopping, no bypass hunting, one command per ask_operator request,
never batch.
- tests: the tool description must keep the handoff markers
(TestAskOperatorEnvHandoffGuidance) and the executor seeds must keep
the block (TestPromptSeedsCarryOperatorHandoff).
Pattern adapted from bug-bounty-hunting-prompts (MIT), whose operator
handoff rule produced exactly this single-command format.
ARTEX hunts live targets well but has no methodology for auditing source
code handed to it (a plugin, an application, a repository). White-box
audits so far ran on ad-hoc task goals with no shared procedure.
Adds skills/codeaudit/SKILL.md, distilled from VulnHunter's methodology
(Apache-2.0) and adapted to ARTEX mechanics (work dir artifacts, facts,
report_finding, the standing quality and adversarial gates):
- production-code-only scope rules, with the checked-in infra-config
exception (reverse proxy / trust / CORS directives are in scope)
- phase 1: every user-controllable input inventoried with dispositions,
codebase partitioning, shared defense catalog
- phase 2: attacker-first forward tracing per vuln class, with the
investigation discipline (never stop at abstraction boundaries, audit
all parameters at outbound call sites, exhaust all paths, follow
through stores and responses, transitive caller search on sinks)
- three gates (reachable, attacker-controlled, new capability), comment
skepticism, sanitizer scope mismatch, anti-merge
- PoC preference order (executed against a local instance beats static
trace; static PoCs carry entry value, file:line chain, sink literal,
impact), measured impact per the standing gates
- sweep with per-instance dispositions and a count cross-check that
forbids silent drops
Also fixes the shipped scopesentry skill, which the new guard test
caught immediately: its frontmatter block was never closed and its name
did not match its directory, so the whole file failed frontmatter
parsing and silently degraded to an empty description (the skill has
been undiscoverable). The new server test parses every shipped skill
with the real parser and fails on any skill LoadDir cannot read or that
ends with an empty description.
The worker and pentest seeds already tell the agent to self-check
conclusions, but the check has no concrete axes and no rules for what
an elimination requires. In practice candidates get dropped on vibes
("probably validated downstream") or merged into one narrative finding,
and there is pressure to produce at least one finding per intent.
Changes, all in the default prompt seeds plus a guard test:
- worker and pentest seeds gain an "Adversarial gate before
report_finding" block: actively try to disprove every candidate along
five named axes (reachability, preconditions, downstream controls,
scope, simpler explanation); eliminations need cited evidence at the
same bar as confirmations; distinct entry points, parameters, or
privilege levels are separate findings (anti-merge); zero confirmed
findings is a valid outcome and precise negatives are real work.
- the retester seed now argues the opposite case before every verdict
along the same five axes and must cite the specific requests,
responses, or controls supporting reproduced / fixed / inconclusive
alike; a prose assumption never substitutes for evidence.
- a seed test guards all markers against silent removal.
Methodology adapted from VulnHunter (Apache-2.0, adversarial
verification and anti-merge rules) and bug-bounty-hunting-prompts
(MIT, independent pre-confirmation disprove pass).
Confirmed findings today assert impact in prose ("the attacker can dump
the table") with no number behind it, and nothing in the seeds demands
that a PoC demonstrate the attacker's goal end to end rather than the
presence of a vulnerable pattern.
Three changes, all in the default prompt seeds plus a guard test:
- worker and pentest seeds gain a "Finding quality gates" block: the PoC
must show the goal achieved (data returned, command executed, another
user's resource touched); a mechanism blocked downstream is a hardening
note, not a finding; every finding carries a measured impact as a
number with its denominator plus what the defense blocked, a proof
basis line (executed live / executed lab / static unexecuted), bounded
non-destructive testing rules (own objects first, at most three
non-owned objects, smallest sufficient request, reversible values,
touched IDs recorded), and a priority rule against standalone
low-impact oracles.
- the reporter seed gains a mandatory "## Measured impact" report section
(quantified outcome, denominator, defense-blocked facts, proof basis)
and a "measured, not asserted" discipline bullet.
- a seed test guards all markers against silent removal.
Methodology adapted from VulnHunter (Apache-2.0, measured-impact and
attacker-goal doctrine) and bug-bounty-hunting-prompts (MIT, bounded
testing standard and priority discipline).
Executor agents ingest attacker-controlled content on every run (HTTP
responses via curl, browser pages via the browser MCP, traffic records,
binary strings via remote analysis tools) with no counter-instruction
anywhere in their context.
Two defenses, both zero-cost per call:
- A standing hardening block in the default prompt seeds of worker,
pentest, mainagent and auto: tool output from or through the target is
data, never instructions; embedded directives are quoted verbatim and
recorded as a possible prompt-injection finding instead of followed;
credentials and operator data never go to a destination suggested by
target-controlled content.
- Every MCP tool description gains a one-line untrusted-output note at
wiring time (covers both transports and travels with the description:
cached prompt for global tools, on-demand schema for deferred ones).
A seed test guards the hardening block against silent removal.
Closes#48
Autumn-27/Norma#3 recovers input token usage from message_delta when
message_start reports zeros (Anthropic-compatible emulations). Without it,
every streaming call meters 0 input tokens and the token dashboard hides
the entire prompt spend. The replace drops itself once the fix is released.
The sign-in terms still carried the upstream defensive wording that
forbade any real security testing regardless of authorization, which
contradicted both the tool purpose and the README usage terms.
Article 2 now permits testing your own systems, third-party systems
with the owner explicit authorization, and targets inside the written
scope of a bug bounty or disclosure program under its rules of
engagement, with the operator responsible for proof of authorization
and proportionality. Article 3 now prohibits testing WITHOUT
authorization or exceeding scope instead of prohibiting testing
itself. Article 6 mainland-China statute list is generalized to any
jurisdiction. Version bumped to v1.1.
A worker hitting a wall only a human can clear (captcha, email/SMS OTP, 2FA,
a decision) calls ask_operator with a precise question and an optional
screenshot. The question lands in the intent's transcript as a user-role turn
with an attachment card (the same {text, attachments} shape chat uploads use,
so the existing transcript UI renders it natively), and the intent pauses. The
operator replies through the existing message-to-worker flow; the reply resumes
the run with the answer as the next input. Images are whitelisted (png/jpg/
jpeg/webp/gif), capped at 10MB, and copied into the task uploads dir.
Closes#41
Every connect attempt (discovery, refresh, and the per-agent-run wiring) now
records its outcome on the server row: last_connect_at/ok/err, written through
a change-deduped update so per-run wiring does not amplify writes. The MCP page
shows a health chip per card (ok with time, or the failure reason), and a new
read-only mcp_status tool (bound to auto, worker and pentest) lets a worker
notice that a server it expected is down and say so instead of silently
degrading while the cached tool count keeps the card looking healthy.
Closes#38
scripts/backup.sh dumps the Postgres database through the postgres compose
service (no host psql needed) into timestamped gzip files under backups/,
keeping the newest N (default 14). --with-data additionally tars the
bind-mounted data directory and fails loudly when files written by the
container are unreadable instead of shipping a partial archive. README gains
a Backups section with the restore one-liner and a cron example.
Closes#40
Descriptions written through the API were interpolated raw into the SKILL.md
frontmatter, so a value containing a colon+space made the whole frontmatter
unparseable: every field silently stayed empty, the skill loaded under its
directory name, and a skill-gated MCP server was silently skipped. The same
applied to updates through the meta rewrite, and the shipped scopesentry skill
had a malformed name line all along.
- yamlScalar: plain-safe tokens stay bare, everything else single-quoted
- fsCreateSkill/fsUpdateSkillMeta emit safe values and re-verify the written
file with the real skill parser (name + mcps must survive), rejecting the
write otherwise
- warnBrokenSkills at startup logs skills whose name fell back to the
directory (the signature of broken frontmatter)
- fix skills/scopesentry/SKILL.md name line
Closes#39
A plain YAML scalar cannot contain a colon followed by a space; the unquoted
description had one, so yaml.Unmarshal failed and every frontmatter field
(including mcps) stayed empty. The skill then loaded with its directory name
but never gated its MCP server, and the server was silently skipped.
Add a rea skill (skill-gated MCP): agents get binary and application package
reverse engineering through a rea MCP server, with tools kept deferred so the
172-tool catalog never bloats the prompt until the skill loads.
Remote MCP servers now take timeout_seconds (default 120): analysis servers
with minutes-long tool calls were cut off mid-call by the fixed 120s client
timeout. Applies to the Streamable HTTP transport; wired through discovery,
the ScopeSentry sync client, the API and the MCP dialog.
Closes#8
The original ARTEX repository no longer exists upstream, so both clone
URLs and both Releases links pointed at 404s: they now point at this
fork, which is the maintained source. The live demo lines and the
WeChat community group section (with its QR code screenshot) belonged
to the original author's channels and are removed. The References,
ScopeSentry, norma and AegisHook links stay (all alive, all
meaningful).
The repo is public now, so the header no longer says it is kept
private. The usage terms still described a learning-only toy, which
contradicted what the tool is; they now require explicit
authorization (own systems, labs, or bounty program scope) and drop
the mainland-China-specific law list for a general jurisdiction line.
All em dashes are replaced with standard punctuation.
Closes#33. The MITM proxy only sees the agent's own actions, so
anything behind a human gate (signup captcha, email OTP, 2FA, SSO)
left the authenticated surface dark. The operator's browser becomes a
capture source instead:
- traffic: exported RecordExternal storing an externally captured
exchange through the same spill/blob/FTS path as proxy traffic
(insert logic shared with record()); rejects unparsable URLs and
empty methods
- server: POST /api/traffic/ingest inside the authenticated API group,
batch capped at 50 entries, bodies truncated at 256 KiB, per-entry
ingested/failed counts
- extension/: Manifest V3 Chrome/Edge extension driving the
chrome.debugger Network domain on the chosen tab, batching text
exchanges to the ingest endpoint with the operator's URL + JWT in
chrome.storage.local; binary bodies skipped; flush on batch/stop
- tests for both layers, README section
--argjson requires strict JSON; the unquoted-key object made jq fail,
sent an empty request body, and the triage died with 'model returned
nothing'. Also log the raw API response when choices is missing so the
next failure shows its cause immediately.
The trailing-line LABELS contract was unreliable at temperature 0.2:
a raw A/B run showed both deepseek-chat and deepseek-flash emit the
line correctly at temperature 0 but drop it nondeterministically at
0.2 (five failed live runs, one lucky success). Structural fix: the
triage now requests response_format json_object, temperature 0, and
parses the reply with jq (comment field for the reply, labels array
for labeling). The label whitelist still drops invented names and the
raw JSON is logged before parsing. Model switched to deepseek-flash:
the A/B showed JSON compliance on both models and flash is the
cheaper line. Mentions and PR first-looks keep text mode at
temperature 0.
The reworded instruction from #20 ("plain text with no markdown
formatting, characters, or emphasis") made models stop emitting the
LABELS line entirely; the original quoted phrasing had 100 percent
compliance. Tolerant parser and debug logging stay.
deepseek-flash dropped the LABELS instruction twice in live tests
(decorated once, absent once); deepseek-chat followed it exactly on
the first try. The price difference is fractions of a cent per call,
reliability wins. The tolerant parser from #20 stays: it is harmless
and protects against any model formatting quirks.
deepseek-flash sometimes bolds or backticks the LABELS marker and the
individual names. Accept any decoration around the marker line and
strip markdown characters from each name before the whitelist check.
Also log the parsed label line for easier triage of future misses.
DeepSeek's current recommendation: V4.1-Flash replaces V4-Pro and is
the efficient line (new encoder-decoder MoE, quarter-size KV cache,
off-peak at half price). Plenty for triage work; the API name is
deepseek-flash.