Workers record per (surface, risk_area) outcomes with the new record_coverage
tool (reported, no_issue_found, ruled_out, not_applicable, needs_follow_up).
Gaps are needs_follow_up entries, entries missing their required evidence or
reason, and goal vulnclass values with no entry (unrecorded_risk_class), so an
absent finding never reads as a clean target. Completeness marks timeout,
budget wrap-up, pause, abort and running tasks as incomplete with a caveat.
Surfaced in the planner context (one line per round, full gap list on the
timeout/budget wrap-up round), GET /api/tasks/{id}/coverage, the coverage card
and the Markdown report. Follows coverage_enabled; no new env var.
ARTEX_UPDATE_CHECK=1 lets the backend check the public GitHub releases API
for the latest tag at most once per 24 h (in-memory cache; failures are not
retried in the window) and report it in GET /api/health when it is newer
than the build version. The web UI shows a dismissible strip linking to the
Releases page. Unset (the default), no update request is made.
An ask_operator question that names a host or URL not among the task's
goal targets is tagged "contains target-derived text": the hosts go into
the transcript activity's Detail payload as target_derived_hosts, and the
transcript shows the tag on the request card when the field is set.
Hosts are read with intercept.ExtractTargets (the target gate's
command-string rules) from both the question and the task goal, and
compared without the port. The text the model reads, the pause, and the
operator's decision are unchanged: the tag is a visible hint only.
Audit F12, 0.7.0.
Auto task_scope rows (source='auto') come from insert_assets and intent
asset_ids, i.e. from target-derived content. They now count toward the
coverage denominator and list_untested_assets only when they sit under a
root the same task declared (a non-auto row: goal target registered via
add_task_scope, or manual scope): the same or a deeper domain of a declared
root_domain/subdomain, or a net inside a declared ip/cidr.
The direct-source coverage view also stops counting an asset from an intent
anchor alone; fact anchors still count, so tested evidence is unchanged.
Without this, insert_assets put a planted host back into the backlog
through its intent anchor.
task_scope is described as "coverage scope, not enforced" in the code
comments, the add_task_scope description, the goal-decomposer tail and the
task overview UI, instead of "authorization boundary".
Audit F8, 0.7.0.
Add two optional free-text fields to findings so a reporter can record
what was tested and did not work (counterevidence) and why the
conclusion holds (confidence_rationale).
- db: idempotent ADD COLUMN IF NOT EXISTS, TEXT NOT NULL DEFAULT '',
no CHECK; RecordFindingInput, the INSERT, the shared select list and
all three scan sites carry them.
- agent: report_finding accepts both as optional strings; the worker
and pentest prompts say to fill them when confidence is not high.
- server: FindingDTO (list and detail) carries both, omitted when empty;
existing databases get the two schema properties added to the stored
report_finding row once, without overwriting user edits.
- web: the finding detail page shows each section only when non-empty.
Both fields stay optional: a missing field never refuses a finding.
PR #172 made the summary length and the number of assets listed in an IM
message configurable, but only through the API. The Notifications page now
shows both next to the other global settings:
Summary length (characters) notify_summary_max_runes 0-4000
Assets listed per finding notify_assets_max_shown 0-50
The values are read from GET /api/settings (they are not part of
/notify/meta) and saved with the existing "Save global settings" PUT.
Each field carries a help text stating that 0 means no truncation.
A value that is not a whole number within the bounds marks the input
invalid and blocks the save with a toast. The check runs before the
request because the backend applies the fields of one PUT in order, so a
400 on a limit would leave the URL and digest interval already saved. An
empty field is not submitted and keeps the stored value.
The option filters decided_by = model, which includes denials the model judge
made, so "AI-approved" was inaccurate. Approved-by-AI is the combination with
status allowed.
The approval list and detail return decided_by exactly as stored, and the
decided_by filter matches that same value. Rows decided before the column
existed show "Legacy" and match no decided_by filter; their decision_source
stays in its own column and nothing is rewritten.
UI: a "Decided by" column (Human, Rule, Model, System) and a matching filter
beside the decision-source filter, including "AI-approved only" for model.
Types and the API query carry decided_by the same way decision_source does.
Show the automated verification outcome next to the human triage status:
a badge on the findings list and detail page ("Verified L3", "Unverified",
"Blocked", "Not assessed", "False positive"), a verification-state filter
next to the status filter (persisted with the other list preferences), and
the verification note on the detail page when the state is not verified.
API: the findings list, groups, asset tree and export accept a
verification_status filter. Without it nothing changes.
Not included: a verified count in the statistics.
Add an optional per-profile User-Agent that overrides the header on every
outbound LLM request. When empty (the default), the existing behaviour is
unchanged and the default User-Agent is left untouched.
The value is trimmed and validated: control characters / newlines (header
injection) and values longer than 256 bytes are rejected. Wired through the
profile config struct, the Postgres llm_profiles schema, the profile API/DTO,
the connection-test path, and the LLM profile form, following the existing
session-header field. Addresses part of #154; the fixed-full-URL mode is not
included.
- POST /api/auth/login: 10 attempts per minute per TCP peer IP
(X-Forwarded-For ignored), then 429 with Retry-After. The block starts
at 1 minute and doubles on each attempt while blocked and on each new
block, capped at 15 minutes. A successful login clears the IP. No
per-account lockout. In-memory, idle entries swept after 15 minutes,
at most 10,000 IPs.
- New passwords (init, change-password, /setup page) need 12 characters.
Login does not check length, so existing 8-character passwords work.
- The binary's -addr default is 127.0.0.1:8787. The Docker image passes
-addr :8787 explicitly and is unchanged.
- GET /api/health returns {"ok":true} without a valid token; with one it
still returns service and version for the web UI top bar.
POST /api/auth/init accepted any caller while auth.password_hash was empty,
so whoever reached port 8787 first could set the admin password and take the
instance. When no password is set at startup, the server now generates a
random one-time setup token and writes it to the server log. authInit
compares it in constant time, rejects a missing or wrong token with 403, and
clears it once the password is stored.
The setup page has a field for the token and api.initPassword sends it.
docker-compose publishes 8787 on 127.0.0.1 only, like 8788. README says where
to find the token.
The update source is hard-coded to a repository that no longer exists and the release checksums are unsigned. Removes the selfupdate package and the /api/update/* endpoints, the top-bar check that queried GitHub on every page load, the Version & Updates card, and the exit-75 swap protocol in the start scripts.
Sessions keep state=running in the store during a task-level pause, so the
sessions tab treated them as live and the run duration kept ticking with
Date.now(), visibly advancing time while the task (and its deadline budget)
was frozen. Pass taskPaused down from the detail page and pin the duration
at the last recorded step while paused.
Closes#84
The token metering is real now (input, output, cache read/write per
profile), but it answers "how many tokens" while the question an operator
actually asks is "what did this run cost". Converting requires per-profile
prices, which nowhere exist.
Changes:
- llm_profiles gains four optional price columns (EUR per 1M tokens:
input, output, cache read, cache write; 0/NULL = unset), migrated
idempotently and round-tripped through SaveProfile / the profile API
in one small auditable write
- the usage aggregation computes cost_eur per profile from the stored
prices, in Go after the archived-task merge so cold-storage tokens are
priced identically; profiles without prices keep cost_eur nil instead
of showing a fake zero
- web types carry cost_eur and the price fields; the dashboard card and
profile-dialog inputs consume them in a follow-up (the API serves the
data already)
- integration test: priced profile + usage rows -> expected EUR to the
cent (2M in @1.5 + 0.5M out @4 + 1M cache-read @0.15 = 5.15), plus a
prices round-trip check
Note: the Go side activates on the next build; the schema migration is
idempotent at startup.
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.
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
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
Bug bounty alias accounts are nearly always emails, so a bare
"username" placeholder read like a handle only. Say "email or
username" in both the creation dialog and the task detail tab.
The agent tool description already said "login name / email used".
The test accounts section was mounted after the scrollable field area
of the New task sheet, so it sat pinned under the scrollbar instead of
scrolling with the other fields, and its Add account button floated in
a header row instead of sitting in the field content like Add row.
Move it inside the scroll container as a proper Field with the add
button below the rows, matching the intercept rules editor pattern.
Also place the credentials TabsContent after assets in the task detail
page to match the TABS array order and use the local id variable.
Task credentials (#3): declare test accounts at task creation, and never
lose the accounts the agent registers on its own.
- task_credentials table (unique per task+domain+username; upsert
overwrites the password in place), idempotent schema migration
- POST /api/tasks accepts credentials; GET/POST/DELETE
/api/tasks/{id}/credentials serve the UI (manual add, e.g. relaying an
OTP-verified account; delete)
- empty password = the "agent generates" mode: the entry surfaces in the
worker's graph overview with an explicit instruction to register that
account with a strong password and record it
- new tools save_task_credential / list_task_credentials for worker and
pentest; the graph overview injects the task's credentials each round
with a standing note: reuse matching entries, always record accounts
you register
- New Task dialog: optional credentials section (domain, username,
password, note) with a dedicated-test-accounts hint
- task detail: Credentials tab, passwords masked with explicit
reveal/copy, agent-recorded badge, light polling so mid-run recordings
appear
One-shot binding migration (settings-flag-guarded, same pattern as the
other seeds) grants the two tools to worker+pentest on databases seeded
before this change; fresh DBs get them via WorkerTools()/the pentest
catalog.
Closes#3
Comments, doc comments, UI labels, log and error messages, LLM prompt
strings, markdown docs, test data and assertions. Identifiers, enum
keys, routes, placeholders and code structure are unchanged.
Notes:
- docs/漏洞流量证据.md renamed to docs/vulnerability-traffic-evidence.md
- tests that exercised CJK specific handling (GBK zip names, Chinese
full text search) now use ASCII equivalents; go build passes and the
test suite matches the pre translation baseline (remaining failures
need a PostgreSQL DSN and fail identically on the original)
- frontend mention syntax now uses English kinds (finding, asset, ...)
matching the backend, chat mentions tests pass