* test(#3514): add failing-first denylist and integrity suites * fix(#3514): deny internal fetch hosts; disclose unverified integrity * docs(#3514): trust-model, glossary, and changeset entries * fix(#3514): scope v6 checks to literals; exact pin kinds in prompt * chore(#3514): backfill changeset pr number --------- Co-authored-by: sim <sim@local>
This commit is contained in:
@@ -202,6 +202,31 @@ interface LifecycleOptions {
|
||||
/** Stamp written onto every capability-owned shared-config entry, for surgical removal. */
|
||||
const CAP_MARKER = '_gsdCapability';
|
||||
|
||||
/**
|
||||
* #3514 (epic #1900 F21c): what KIND of pin (if any) the source content was pinned with — shared
|
||||
* by the install and upgrade verdicts so the two cannot drift. Reaching the verdict with a
|
||||
* supplied `--integrity` pin means the pin VERIFIED (the resolver throws on mismatch). A git
|
||||
* `#sha:<40-hex-commit>` ref is the git analog of a hash pin — a commit checkout, verified by
|
||||
* spelling: `/^sha:[0-9a-f]{7,40}$/i` accepts only a hex commit id, so a mutable ref
|
||||
* (`#sha:main`, `#sha:v1`) is NOT counted as a pin (isolated review finding — a moving ref must
|
||||
* never render as pinned). Everything else stages with no pin and must say so in the consent
|
||||
* prompt.
|
||||
*/
|
||||
type IntegrityPin = 'sha512' | 'git-commit' | 'none';
|
||||
|
||||
const GIT_SHA_PIN_RE = /^sha:[0-9a-f]{7,40}$/i;
|
||||
|
||||
function resolveIntegrityPin(
|
||||
integrity: unknown,
|
||||
parsed: { kind?: string; ref?: unknown },
|
||||
): IntegrityPin {
|
||||
if (typeof integrity === 'string' && integrity.length > 0) return 'sha512';
|
||||
if (parsed.kind === 'git' && typeof parsed.ref === 'string' && GIT_SHA_PIN_RE.test(parsed.ref)) {
|
||||
return 'git-commit';
|
||||
}
|
||||
return 'none';
|
||||
}
|
||||
|
||||
/** Keys that must never be used as object indices (prototype-pollution guard). */
|
||||
function isUnsafeKey(k: string): boolean {
|
||||
return k === '__proto__' || k === 'constructor' || k === 'prototype';
|
||||
@@ -1023,6 +1048,7 @@ async function installCapability(spec: string, opts: LifecycleOptions): Promise<
|
||||
stagedDir,
|
||||
strictKnownRegistries,
|
||||
hostVersion,
|
||||
integrityPin: resolveIntegrityPin(opts.integrity, parsedPre),
|
||||
});
|
||||
|
||||
if (!verdict.allowed) {
|
||||
@@ -1225,6 +1251,7 @@ async function upgradeCapability(spec: string, opts: LifecycleOptions): Promise<
|
||||
stagedDir,
|
||||
strictKnownRegistries,
|
||||
hostVersion,
|
||||
integrityPin: resolveIntegrityPin(opts.integrity, parsedPre),
|
||||
});
|
||||
if (!verdict.allowed) {
|
||||
return { status: 'blocked', disclosure: verdict.disclosure, blockReasons: verdict.blockReasons };
|
||||
|
||||
Reference in New Issue
Block a user