Metadata-Version: 2.4
Name: travis234-websec-mcp
Version: 0.1.2
Summary: Independent bounded HTTP security workbench for MCP clients and Travis234.
License-Expression: MIT
Requires-Python: <3.14,>=3.13
Description-Content-Type: text/markdown
Requires-Dist: mcp<3,>=2.2
Requires-Dist: pydantic<3,>=2.12
Requires-Dist: httpx<1,>=0.27
Requires-Dist: httpcore<2,>=1
Requires-Dist: anyio<5,>=4
Requires-Dist: ahocorasick-rs<2,>=1.0.3
Requires-Dist: travis234-mcp-adapter<0.5,>=0.4

# Travis234 Websec MCP

An optional external HTTP workbench for Travis234 and standard stdio MCP clients.
Python 3.13 and macOS or Linux are required. Travis core and its existing MCP
adapter supply the host integration; this package owns requests, HTTP execution,
sanitization and evidence. KHackBar is a workflow reference and is not a dependency.

This milestone supports individual request experiments. Payload catalogs,
generators, batch fuzzing, login/token extraction and browser capture remain
future work. The package also includes imported assessment skills, built into
Travis and exposed to other clients through MCP
resources and prompts. These guides do not install external scanners. See the repository's
[complete KHackBar feature mapping](../../docs/verification/2026-10-18-khackbar-feature-crosscheck.md).

## Build, install and activate

For the published package, stop existing Travis sessions, then install or upgrade:

```sh
travis234 install 'travis234-websec-mcp==0.1.2'
```

Start a new Travis process after upgrading. Existing running processes retain
their installed descriptor, environment and snapshots until they exit.

Run these commands from the Travis234 repository root:

```sh
uv sync --project packages/travis234-websec-mcp --locked --group source-test
websec_dist_dir="$(mktemp -d "${TMPDIR:-/tmp}/websec-dist.XXXXXX")"
uv build --wheel --out-dir "$websec_dist_dir" packages/travis234-mcp-adapter
uv build --wheel --out-dir "$websec_dist_dir" packages/travis234-websec-mcp
export PIP_FIND_LINKS="$websec_dist_dir"
export UV_FIND_LINKS="$websec_dist_dir"
websec_wheel_uri="$(packages/travis234-websec-mcp/.venv/bin/python -c 'from pathlib import Path; import sys; print(next(Path(sys.argv[1]).glob("travis234_websec_mcp-*.whl")).resolve().as_uri())' "$websec_dist_dir")"
travis234 install "travis234-websec-mcp @ $websec_wheel_uri"
travis234 --tools read,bash,websec
```

Installation registers the package-owned server and one `websec` facade through
the shared adapter. Discovery and `status` do not send a testing request. Tool
selection uses the existing `--tools` allowlist; there is no new Travis flag.

For another MCP client, install the wheel and its dependencies into that client's
Python 3.13 environment, then configure this stdio command:

```sh
travis234-websec-mcp mcp --transport stdio
```

The standalone server does not import the Travis extension. It uses the same
configuration, request bounds and evidence format.

## First setup and profile discovery

Call `status` first. It returns `configuration_path`, the exact configured
profile names, authentication readiness and the server-process lifetime of IDs.
Profile names are operator-defined; `default` becomes available only if you
create a profile with that name.

When configuration is missing, `status.data.setup.command` contains an executable
argument list for the installed server's Python interpreter. Replace its example
origin with your exact authorized origin and run it in the server's environment.
This also works when Travis has installed the package in a private environment
and its console command is not on your shell's PATH. With the console command
available, the equivalent is:

```sh
travis234-websec-mcp init --profile default --origin https://api.example
```

Repeat `--origin` to authorize additional exact origins. Init validates all inputs
before creating state, creates a mode-0600 config, and refuses to overwrite an
existing file or follow a symlinked Websec directory. It creates a profile like:

```json
{
  "version": 1,
  "profiles": {
    "default": {
      "origins": ["https://api.example"],
      "headers": {"User-Agent": "travis234-websec-mcp/0.1.2"},
      "deadline_seconds": 10
    }
  }
}
```

This profile needs no environment variables. Restart Travis after setup, call
`status`, and use the returned name exactly in `prepare`. Status and discovery
remain read-only and never create a profile or authorize an origin automatically.

For an existing config, edit the file reported by `configuration_path`. An
`invalid_config` diagnostic identifies its profile, field, reason and corrective
hint without returning header values or malformed JSON content. JSON syntax
errors include line and column. A missing config means no profiles exist yet;
guessing a profile name cannot create one.

### Headers and the launch environment

Profile header values accept printable ASCII literal strings, `${NAME}`
interpolation, and exact `$env:NAME` references. For example, a literal
`"User-Agent": "my-retest/1.0"` is valid without exporting `USER_AGENT`.
Credentials should use references such as `"Authorization": "Bearer ${WEBSEC_TOKEN}"`.
Available configured header values remain subject to existing redaction rules.

Referenced variables must be set in the environment that launches Travis.
Exporting them in a later tool shell, or editing `~/.zshenv`, cannot change an
already-running parent's environment. Reconnecting MCP starts a child of that
same parent; restart the entire Travis process with the variables available.
The native Python CLI does not load `.env` for this package. The npm sandbox
launcher can receive an explicitly selected file through its existing
`--dotenv /path/to/.env` option; see the root README for that launch boundary.

Status reports missing or invalid reference names under each profile's
`authentication` object. Send returns the same names in `error.details` before
opening a connection; values are never included. Configuration and environment
are captured at server startup.

Prepare responses are sanitized previews. Password/token fields and known
credentials can appear as `[REDACTED]` while the private immutable request retains
the original bytes for sending. Keep credentials out of tool arguments: the host
records inputs before the plugin can sanitize them.

## Imported assessment skills

The source index is
[htooayelwinict/awesome-ai-offensive-security](https://github.com/htooayelwinict/awesome-ai-offensive-security/tree/0e3180f4a0ffd5b6703943cf112d85017895a842).
That repository contains links; the actual skills are imported from these
linked MIT-licensed projects at fixed revisions:

| Source | Included skills |
| --- | --- |
| [SnailSploit/Claude-Red](https://github.com/SnailSploit/Claude-Red/tree/a8de813edc9f66f60d44cfe42d47750e89d9930b) | `offensive-api-security`, `offensive-jwt`, `offensive-idor`, `offensive-ssrf`, `offensive-ssti`, `offensive-ai-security`, `offensive-reporting` |
| [transilienceai/communitytools](https://github.com/transilienceai/communitytools/tree/95fdc128af4ca1ae16b3226f9430f1bad97b0656/skills) | `web-app-logic`, `client-side`, `authentication`, `injection`, `server-side`, `reconnaissance`, including their reference and scenario files |

All thirteen skills ship as read-only built-ins in Travis's Python and npm
distributions, using its existing lazy loader, precedence and trust rules.
Installing this optional package adds the Websec tools. When the user asks to
use Websec MCP, its facade instructs the agent to try loading the original eight
SKILL.md guides before its first Websec call, like Evidence MCP's research
skill. This guidance also accompanies authorized delegation. User overrides
and `--no-skills` remain effective; unavailable guides are reported. These eight
guides cover API, JWT, IDOR, SSRF, SSTI, AI security, reporting and business logic.
The five added guides cover client-side behavior, authentication, injection,
server-side behavior and reconnaissance. The system prompt routes to them by
task and loads their references progressively. The package does not register duplicate
skill directories. A standalone installation exposes the same documents
through MCP resources and prompts. No skill is copied into the user's Codex installation.

Each imported skill has a `TRAVIS234.md` guide describing the supported tools
and request workflow. Local changes add compatible YAML metadata and a link to
that guide. The five new entrypoints retain their original guides as `UPSTREAM.md`.
Upstream methodology and references are retained; executable helpers are not
installed and their source paths are recorded in the manifest. Each skill keeps
its upstream license; [SOURCES.json](travis234_websec_mcp/resources/skills/SOURCES.json)
records repository revisions, source paths, original hashes and shipped hashes.
Imports are bundled: startup, discovery and reads do not fetch updates or run
upstream code.

For a standard MCP client:

- `resources/list` enumerates skill documents in pages of eight. Pass its
  `nextCursor` back as `cursor` until it is absent.
- Read `websec://skills/index` for the skill index, then a skill's
  `websec://skills/<name>/TRAVIS234.md` and `websec://skills/<name>/SKILL.md`.
- A document larger than 4 KiB returns an ordered part index. Read the listed
  `?part=1`, `?part=2`, … URIs in order. All bytes are retained, with UTF-8
  characters kept intact. Relative references resolve within the skill's URI
  directory. Each part is at most 4 KiB before protocol encoding. When literal
  code examples cannot pass the existing text classifier unchanged, the read
  uses a standard MCP blob with the same MIME type: Base64-decode it as UTF-8
  to recover the exact text.
- `prompts/list` exposes thirteen entry prompts, named `websec-<skill-name>`.
  `prompts/get` takes that name and no arguments; it directs the client to the
  integration guide and relevant upstream resources.

Resource reads accept only bundled catalog URIs. Unknown URIs, prompts and
cursors return fixed errors. The original document is checked before any part
is returned, and the existing final MCP frame checks still apply. When a known
credential overlaps a document, its read fails without returning that document.

The existing seven request operations retain their contracts; `inspect` adds
offline HTML-resource observations. Browser sessions,
raw protocol experiments, parallel probes and external tools mentioned in a
guide require those capabilities separately. Imported methodology does not
enable them in the Websec executor.

## Try the actual acceptance fixture

In one terminal, start the bundled fixture. It prints its actual loopback origin:

```sh
packages/travis234-websec-mcp/.venv/bin/python packages/travis234-websec-mcp/tests/http_fixture.py
```

Keep it running. In a second terminal, copy that origin into
`WEBSEC_FIXTURE_ORIGIN`, then create an isolated lab configuration:

```sh
export WEBSEC_FIXTURE_ORIGIN='http://127.0.0.1:<printed-port>'
export WEBSEC_LAB_TOKEN='synthetic-lab-token'
websec_lab_dir="$(mktemp -d "${TMPDIR:-/tmp}/websec-lab.XXXXXX")"
export TRAVIS234_CODING_AGENT_DIR="$websec_lab_dir/agent"
packages/travis234-websec-mcp/.venv/bin/python - <<'PY'
import json
import os
from pathlib import Path
from urllib.parse import urlsplit

origin = os.environ['WEBSEC_FIXTURE_ORIGIN']
assert urlsplit(origin).hostname == '127.0.0.1' and urlsplit(origin).port
path = Path(os.environ['TRAVIS234_CODING_AGENT_DIR']) / 'websec' / 'config.json'
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps({'version': 1, 'profiles': {'lab': {
    'origins': [origin],
    'headers': {'Authorization': 'Bearer ${WEBSEC_LAB_TOKEN}'},
    'deadline_seconds': 10,
}}}))
PY
```

Run installation and activation in that same terminal so Travis inherits the
directory override and credential reference. Stop the fixture with Ctrl-C.
Normal operator configuration lives at `~/.travis234/agent/websec/config.json`;
the existing `TRAVIS234_CODING_AGENT_DIR` override changes the agent directory.
The package never loads `.env` files. Headers accept literal strings, exact `$env:NAME`
references or `${NAME}` interpolation. Missing or empty references leave offline operations
available and make `send` return `authentication_unavailable` before connecting.

## Request workflow

Call `websec` with the following operation arguments. Replace the fixture origin
with its printed value and copy each returned ID exactly:

| Operation | Arguments and result |
| --- | --- |
| `status` | `{}` or `{"operation":"status"}` returns configuration, profiles and limits. |
| `prepare` | `{"operation":"prepare","profile":"lab","request":{"url":"<fixture-origin>/query-echo?q=original&q=untouched"}}` creates an immutable request ID. Optional request fields are `method`, header pairs and a UTF-8 `body`. |
| `mutate` | `{"operation":"mutate","request_id":"<original-id>","location":"query","index":0,"value":"changed"}` creates a new ID. Header mutation is also indexed; body mutation replaces the whole body. |
| `transform` | `{"operation":"transform","value":"a b","codec":"url","direction":"encode"}` returns `a%20b`. Codecs are `url`, `base64`, `hex`, `double_url`, `html`, `unicode` and `reverse`; both directions are supported. |
| `send` | `{"operation":"send","request_id":"<request-id>"}` executes that snapshot once. Send the original and changed IDs separately. |
| `inspect` | `{"operation":"inspect","response_id":"<response-id>"}` observes external script and stylesheet attributes in retained HTML without another HTTP exchange. |
| `compare` | `{"operation":"compare","left_response_id":"<original-response-id>","right_response_id":"<changed-response-id>"}` returns measured differences, completeness and an optional sanitized diff. |
| `export` | The same response pair with `"operation":"export"` writes a new evidence file and returns its basename, ID and byte count. |

The fixture receives `original`, then `changed`, with the second query entry
unchanged. Its UTF-8 response bodies have 8 and 7 bytes respectively. Reusing the
original ID sends the original value again. `prepare` also accepts strict raw
HTTP/1.1 text using `raw_request` and an explicit `origin`, instead of `request`.
Ambiguous framing, invalid fields and unavailable IDs return specific bounded
errors. Comparisons report observations rather than vulnerability verdicts.

### Additional text codecs

| Codec | Defined behavior |
| --- | --- |
| `double_url` | Apply the existing UTF-8 URL conversion exactly twice. Decoding rejects malformed percent escapes or invalid UTF-8 at either step. A literal `+` stays `+`. |
| `html` | Encode the five HTML special characters; the apostrophe becomes `&#x27;`. Decode named/numeric entities once using HTML5 rules. Markup stays text: decoding `&lt;b&gt;é&lt;/b&gt;` returns `<b>é</b>`. |
| `unicode` | Encode each UTF-16 unit as a lowercase `\uXXXX` escape, including valid pairs for supplementary characters. Decode those escapes once, combine valid surrogate pairs and reject malformed or unpaired units. Other backslash sequences and percent escapes stay literal. |
| `reverse` | Reverse Unicode code points. Both directions perform the same operation; `A😀é` becomes `é😀A`. Combining marks reverse individually. |

These operations retain the 16 KiB input and 64 KiB output limits. The aggregate
MCP frame limit also applies, so a valid engine result can return `output_limit`
at the public boundary. Errors never return a successful partial transformation.
Known credentials are checked in both the source value and the derived result.

## Bounds, credentials and lifetime

Origins are exact normalized scheme/host/port allowlists. Every send checks its
final target. The client performs one exchange, follows no redirects, retries
nothing, carries no cookie jar, verifies TLS and ignores proxy environment
variables. Status advertises `max_concurrent_sends: 1`. Send sequentially and
wait for the active exchange; an overlapping send returns `busy` with a hint.
Cancellation and shutdown close and join owned HTTP work.

| Resource | Limit |
| --- | --- |
| URL / request body / raw import | 8 / 64 / 128 KiB |
| Wire headers, including authentication and framing | 100 entries / 32 KiB |
| Transform input / output | 16 / 64 KiB |
| Deadline | Default 10 seconds; profile integer 1–30. A caller may only lower it with `timeout_seconds`. |
| Retained response / text preview | 1 MiB / 8 KiB |
| Static HTML inspection | 10,000 start/end elements; up to 32 resources, 2 KiB per attribute and 8 KiB of resource entries |
| Requests / responses in memory | 32 / 16; unpinned oldest entries are evicted. |
| Complete outgoing MCP frame / exported JSON | 32 KiB / 4 MiB |

Compressed responses are reported as `unsupported_encoding` and are not decoded.
Binary and incomplete responses preserve honest byte measurements; comparison
does not infer complete text equality or an exact complete-body length from them.
Incomplete or oversized presentation values are withheld in full, with explicit
omission flags. Complete bounded JSON can retain unrelated public fields.

### Body diagnostics and multiple HTML resources

Read `body_text_omitted_reason` alongside `complete`, `observed_body_bytes`,
`retained_body_bytes` and `body_text_clipped`:

| Reason | Meaning |
| --- | --- |
| `redacted` | The privacy filter withheld the text value. |
| `analysis_limit` | Bounded filtering could not approve the text; this does not establish a credential match. |
| `incomplete` | The response capture is partial, including a response-size cutoff or interrupted exchange. |
| `output_limit` | Safe text exceeds the preview allowance. |
| `binary` | No supported UTF-8 text view is available. |
| `null` | No whole-text omission; individual JSON fields can still be sanitized. |

`[REDACTED]` alone does not distinguish those cases. Byte measurements describe
the captured body, even when its public preview is withheld.

Use `inspect` on a retained HTML response for content or SRI review. Each
external script or stylesheet has an `index`, element type, line/column, URL,
`integrity`, `crossorigin`, `type` and duplicate-attribute flag. Assess each
resource separately; those indices are unrelated to query/header mutation
indices. Inline script contents and input values are excluded, and embedded
resource URLs such as `data:` are withheld. Attributes remain sanitized.

Check `capture_complete`, `inspection_complete` and `limitations` before making
absence claims. Static attributes do not validate SRI hashes, JavaScript
execution, browser policy or security impact. Use an available browser when
needed. Returned URLs do not expand authorized origins or authorize fetching
every resource. HTML exports include the same sanitized observations and body
diagnostics; exports do not reveal raw withheld bodies.

Keep credentials in the operator environment and profile references. Never paste
them into tool arguments: the host records those inputs before the plugin sees
them. Available referenced values and bounded URL/Base64/hex forms are sanitized
from returned results and exports. Authentication values go only to an allowed
origin's request.

Request and response IDs belong to one server process. Shutdown, reload or
eviction can invalidate them. Exports persist under
`<agent-directory>/websec/evidence/` as new atomic mode-0600 JSON files; the tool
does not accept arbitrary export paths or overwrite prior evidence.

Restart the entire Travis process after changing profile configuration,
credential references/environment or the installed package. Registrations are
process-local; an existing host does not replace a different descriptor in place.
Stop Travis before removing the package, then start a new process to confirm the
facade is absent. Removing the package preserves exported evidence.

## Qualification

```sh
PYTHONDONTWRITEBYTECODE=1 packages/travis234-websec-mcp/.venv/bin/python -m pytest -q -p no:cacheprovider packages/travis234-websec-mcp/tests
```

The acceptance test builds actual wheels, installs through Travis's package
manager in a fresh interpreter outside the checkout, binds a real session, uses
a deterministic model stream and real HTTP/MCP transports, scans persisted
session JSONL, and checks A1–A13. Container qualification uses the same driver
with offline wheels, a nonroot user and Docker networking disabled. A build or
unit-test pass alone does not establish installed acceptance or deployment.
