Files
Jamie Tanna 7386ea49c4 refactor(host-rules): add an inherited trust tier
As part of looking to allow Inherit Config to manage Host Rules' grants,
we need to perform a prefactor to create an additional trust tier
between the global self-hosted administrator's config, and presets/repo
config.

The `find()` method already resolves sensitive fields - `headers`,
`enabled`, and the computed internal-host grant - a "trust tier" at a
time, so that the outcome cannot depend on how specific a rule from a
less trusted tier makes itself. The new `inherit` tier slots in between
the two we had:

- `headers` are applied untrusted first, then inherited, then the
  administrator's, so theirs still win outright
- `enabled` is taken from the administrator's rules, falling back to
  inherited config's, then a repository's
- an administrator's `allowInternal: false` acts as a veto: it is
  resolved before inherited config's rules, and clears the scoped
  and implicit grants too, so inherited config cannot re-grant the
  host in any shape by out-scoping it. Meanwhile any inherited rule
  naming a host in its `matchHost` - credentialed or not - now
  implicitly permits it, like the admin's own rules do

We introduce a separate field than `trusted` to avoid Inherit Config
gaining the same precedence as the admin themselves.

Co-authored-by: Claude Fable 5 <jamie.tanna+claude-code@mend.io>
Co-authored-by: Claude Opus 5 <jamie.tanna+claude-code@mend.io>
2026-09-10 14:52:01 +01:00

73 lines
2.6 KiB
TypeScript

export interface HostRule {
authType?: string;
token?: string;
username?: string;
password?: string;
insecureRegistry?: boolean;
timeout?: number;
abortOnError?: boolean;
abortIgnoreStatusCodes?: number[];
/**
* Whether requests to this host may reach internal addresses.
*
* As they have been marked as trusted by the administrator:
*
* - when `internalHostAccess=block`, these are not blocked
* - when `internalHostAccess=warn`, no warning occurs
*
* This is only allowed in global self-hosted configuration, and if found in repository or preset config, it is stripped.
*/
allowInternal?: boolean;
enabled?: boolean;
enableHttp2?: boolean;
concurrentRequestLimit?: number;
maxRequestsPerSecond?: number;
headers?: Record<string, string>;
maxRetryAfter?: number;
keepAlive?: boolean;
artifactAuth?: string[] | null;
httpsCertificateAuthority?: string;
httpsPrivateKey?: string;
httpsCertificate?: string;
encrypted?: HostRule;
hostType?: string;
matchHost?: string;
resolvedHost?: string;
readOnly?: boolean;
}
/**
* How the trusted host rules treat internal-host access for a request, computed by `find()`. Never settable through configuration: only rules from the administrator's own config, and from organization-inherited config the administrator has opted into trusting through `inheritConfigTrusted`, contribute to it.
*
* The administrator's own rules are resolved first, and an `allowInternal: false` of theirs vetoes every field below: inherited config can never re-grant a host the administrator refused, in any grant shape.
*/
export interface InternalHostGrant {
/**
* The `allowInternal` value of the matching trusted rules, with the most specific rule winning, and the administrator's own rules winning over inherited config's. `undefined` when none of them set it.
*/
explicit?: boolean;
/**
* As `explicit`, but counting only rules deliberately scoped by a `hostType` or by a URL-prefix `matchHost`. Surfaces where a grant must be deliberate - such as HTTP presets, whose responses become config - check this value instead of `explicit`.
*/
scoped?: boolean;
/**
* Whether any trusted rule naming a host (`matchHost`) matched the request: hosts named in configuration the administrator trusts are implicitly permitted. Always `false` under an administrator's veto.
*/
implicit: boolean;
}
export type CombinedHostRule = Omit<
HostRule,
| 'encrypted'
| 'hostType'
| 'matchHost'
| 'resolvedHost'
| 'readOnly'
| 'allowInternal'
> & {
/** computed by `find()` - see {@link InternalHostGrant} */
internalHostGrant?: InternalHostGrant;
};