agap-mcp: authenticate the :3100 listener (kb#180), pin bw CLI, add capture/classifier
Listener auth (kb#180, DESIGN-a2a-agents.md §4)
-----------------------------------------------
agap-mcp binds :3100 on every interface (network_mode: host) and the LAN
carries VPN-terminated peers, so an unauthenticated JSON-RPC listener handed
ha_call_service / gitea_wiki_write / wiki_edit / radicale+todoist writes and
POST /capture-idea to any LAN peer. Only vw_* was gated before (kb#147), and
only at ENFORCE=1.
src/listener-auth.js now requires `Authorization: Bearer <token>` resolving to
a known agent id on every route except /health, which stays open so a
misconfigured token map is still diagnosable. Two gates stay deliberately
layered and independently switchable: "are you an agent at all?" (this file)
vs "are you trusted enough for the vault?" (trust-gate.js), both reading the
same token map.
Also closes an SSE session-hijack hole: /messages previously trusted any
sessionId with no credential, so a guessed or leaked id was full tool access.
Sessions are now pinned to the caller identity captured at the /sse handshake,
comparing agent id *and* token.
Auth defaults ON, and boot fails loudly if the token map is empty rather than
serving 401 to everyone while /health reports ok. Rollback is
AGAP_MCP_REQUIRE_AUTH=0.
Verified live: unauthenticated and bad-token /mcp -> 401, unauthenticated
/capture-idea -> 401, /health -> 200, both real agent tokens -> 200 with 36
tools, including from inside the adolf container.
Pin the bw CLI
--------------
The Dockerfile installed @bitwarden/cli unpinned. Rebuilding jumped
2026.2.0 -> 2026.7.0, whose WASM cipher deserializer rejects any stored login
carrying `"uri": null` ("invalid type: JsValue(Object({...})), expected a
string") -- 33 of 49 items in this vault have that shape. `bw list` then exits
1, server init fails, and the container crash-loops. Pinned to 2026.2.0.
Do not unpin: 2026.7.0 cannot authenticate against this Vaultwarden
(2025.12.0) at all -- it refuses plain HTTP outright and 404s on the identity
endpoint over HTTPS. Updating the CLI requires upgrading Vaultwarden first.
capture / classifier
--------------------
Adds the POST /capture-idea REST endpoint and the idea classifier behind it
(consumed by the todoist-capture plugin), with tests. Carried in the same
commit because server.js wires both this and the auth boot path.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,20 @@ import { writeFileSync, mkdirSync } from 'fs';
|
||||
import { tmpdir } from 'os';
|
||||
import { join } from 'path';
|
||||
|
||||
// Askpass helper: git invokes this script (path is what shows up in ps/args),
|
||||
// and it reads the actual token from an env var — never from argv or the URL.
|
||||
// This keeps the token out of the process table and out of any git error text.
|
||||
let _askpassPath = null;
|
||||
function askpassScript() {
|
||||
if (_askpassPath) return _askpassPath;
|
||||
const dir = join(tmpdir(), 'agap-mcp-wiki');
|
||||
mkdirSync(dir, { recursive: true });
|
||||
const scriptPath = join(dir, 'git-askpass.sh');
|
||||
writeFileSync(scriptPath, '#!/bin/sh\nprintf %s "$GITEA_ASKPASS_TOKEN"\n', { mode: 0o700 });
|
||||
_askpassPath = scriptPath;
|
||||
return scriptPath;
|
||||
}
|
||||
|
||||
const BASE = () => process.env.GITEA_URL || 'http://localhost:3000';
|
||||
let _token = null;
|
||||
|
||||
@@ -48,9 +62,19 @@ export async function giteaWikiRead(page, repo = 'alvis/AgapHost') {
|
||||
|
||||
export async function giteaWikiWrite(page, content, message, repo = 'alvis/AgapHost') {
|
||||
const dir = join(tmpdir(), 'agap-mcp-wiki');
|
||||
const wikiUrl = `${BASE().replace('http://', `http://alvis:${token()}@`)}/alvis/AgapHost.wiki.git`;
|
||||
// Username in the URL is not secret; the password/token is supplied out-of-band
|
||||
// via GIT_ASKPASS + GITEA_ASKPASS_TOKEN, so it never appears in the URL, the
|
||||
// execSync command string, ps/process args, or surfaced git error output.
|
||||
const wikiUrl = `${BASE().replace('http://', 'http://alvis@')}/alvis/AgapHost.wiki.git`;
|
||||
|
||||
const gitEnv = { ...process.env, GIT_AUTHOR_NAME: 'agap-mcp', GIT_AUTHOR_EMAIL: 'allogn@gmail.com', GIT_COMMITTER_NAME: 'agap-mcp', GIT_COMMITTER_EMAIL: 'allogn@gmail.com' };
|
||||
const gitEnv = {
|
||||
...process.env,
|
||||
GIT_AUTHOR_NAME: 'agap-mcp', GIT_AUTHOR_EMAIL: 'allogn@gmail.com',
|
||||
GIT_COMMITTER_NAME: 'agap-mcp', GIT_COMMITTER_EMAIL: 'allogn@gmail.com',
|
||||
GIT_ASKPASS: askpassScript(),
|
||||
GIT_TERMINAL_PROMPT: '0',
|
||||
GITEA_ASKPASS_TOKEN: token(),
|
||||
};
|
||||
|
||||
try {
|
||||
execSync(`git -C ${dir} pull ${wikiUrl} main`, { env: gitEnv, stdio: 'pipe' });
|
||||
|
||||
Reference in New Issue
Block a user