Development record
memory ingest writes a URL credential into the committed store: materialFromFetched sets both the page origin and the page title from the fetched final URL verbatim, so basic-auth userinfo (user:pass@) and credential-shaped query keys survive into the page frontmatter's source.citation, into .sources_index.json (origin and consumers.memory.citation) and into the IngestResult.Citation the --json render prints. redactText cannot see an opaque basic-auth password, so the scanner backstop does not catch it. The six 'fetch failed for %s' sites in acquireSource, defaultFetch and readFetchedResponse echo the raw source the same way, as do the byte-cap refusal and the redirect-count refusal. CWE-522/CWE-532. The fix must clear the userinfo and drop the credential-shaped query keys (token, api_key, apikey, access_token, password, secret; case-insensitive) where the final URL becomes the stored origin and title, and route every fetch-failure message through the redacting renderer.
memory ingest writes a URL credential into the committed store: materialFromFetched sets both the page origin and the page title from the fetched final URL verbatim, so basic-auth userinfo (user:pass@) and credential-shaped query keys survive into the page frontmatter's source.citation, into .sources_index.json (origin and consumers.memory.citation) and into the IngestResult.Citation the --json render prints. redactText cannot see an opaque basic-auth password, so the scanner backstop does not catch it. The six 'fetch failed for %s' sites in acquireSource, defaultFetch and readFetchedResponse echo the raw source the same way, as do the byte-cap refusal and the redirect-count refusal. CWE-522/CWE-532. The fix must clear the userinfo and drop the credential-shaped query keys (token, api_key, apikey, access_token, password, secret; case-insensitive) where the final URL becomes the stored origin and title, and route every fetch-failure message through the redacting renderer.
Sibling sweep
Every site in internal/core/memory that interpolates a fetched URL into a
message or a stored field is fixed here: the six fetch failed for %s sites,
the byte-cap refusal, the redirect-count refusal, and the origin/title
assignment in materialFromFetched. The scheme refusals already rendered
through redactedSource / url.URL.Redacted.
Out of scope, named rather than fixed:
internal/core/cite/fetch.goechoes the checked URL intoCheckOutcome.URLand its messages. Its input is a citation already committed to a document rather than an operator-typed source, so it is a different trust boundary; the same file's unpinned redirect scheme is already recorded as iss-2609012037440084.internal/core/update/update.gointerpolates a release tag, not a caller-supplied URL, and already masks a redirect hop withRedacted().
Grounds
- pursued: a stored origin is a citation, not a way back in, so the credential is dropped structurally by name rather than left to a scanner that cannot see an opaque password; the address is otherwise reproduced byte-identical, and a citation that lost a legitimate query parameter would show the key list is too wide
Frontmatter
| id | iss-2609020238535054 |
|---|---|
| type | issue |
| lifecycle | resolved |
| severity | major |
.abcd/work/issues/resolved/iss-2609020238535054-memory-ingest-writes-a-url-credential.md
open on GitHub ↗ · commit history ↗
mentions1
- iss-2609012037440084The docs cite refresh fetcher in cite/fetch.go re-guards the host per redirect hop through urlguard.CheckHostWith but pins no scheme, so an https link that redirects to plaintext http is followed — the same shape GHSA-35fj-9w6f-7h62 closes in memory ingest. It fetches documented links for liveness and stores no content, so it sits outside that advisory and is recorded here instead of fixed. A fix would refuse a hop whose URL scheme is not https before the host guard, as update.go and memory ingest do.