Why must a curated internal package feed fail closed on unknown internal names?
answer
- one source, not a preferred source
- a miss must be an error
- silent success is the bad outcome
- every resolver, not one config file
- canary name that never resolves
basics
~20 sA miss on a name in your own namespace must be a visible build error, never a fetch from somewhere else. Failing closed stops the build; failing open quietly succeeds with an artifact nobody in your organisation published.
solid answer
~50 sThe property you are buying is: one resolvable source, and no outward fallback for names in your own namespace. If a build asks for an internal-prefixed package the feed has never held, the only acceptable outcome is an error. The moment a miss is allowed to fall through to a public index, an artifact you never authored can end up inside your build with the name of something you trust, and the build will look completely normal. Failing closed converts that into a loud, boring failure someone fixes in minutes. The property has to hold at every resolver — CI, laptops, base images, nested builds — and one config that still lists a public source re-opens the hole for everyone. So you test it rather than assume it, typically by asking CI for a name in your namespace that has deliberately never been published and asserting the build fails.
go deeper
Know the two outcomes by name: fail closed means the build errors on an unknown internal name, fail open means it fetches something from elsewhere and succeeds. Be able to say which one you want.
Explain why silent success is worse than a broken build, and name at least two places the fallback leaks back in, such as base images or a developer machine's default configuration.
Show how you verify the invariant continuously rather than trusting configuration, and pair the closed door with an intake path fast enough that nobody routes around it.
Own the friction you are creating across the estate. Be ready to argue the tradeoff to engineering leadership and to fund the intake path, because an unfunded gate is bypassed rather than obeyed.
## The invariant A curated feed is worth having only if it is **the** source, not *a* source. Stated as an invariant: for any package name inside our own namespace, the curated feed is authoritative, and a name it does not hold does not exist. A request for such a name must terminate in an error. The alternative — a miss quietly continuing outward to a public index — means that whether your build gets your code or somebody else's depends on facts about the outside world that you do not control and cannot see. That is not a configuration preference; it is the difference between a build with a defined set of inputs and a build with an open one. ## Fail closed versus fail open, concretely **Fail closed:** the resolver reports that the name is unknown, the build stops, and a human reads a plain error message. Cost: a broken build. Recovery: minutes, and the failure is in front of the person who caused it. **Fail open:** the resolver finds *something* elsewhere with that name, downloads it, and the build goes green. Cost: an artifact of unknown authorship inside your product, with the name of a component your engineers trust by sight. Nothing in the logs looks wrong. This is the failure mode that matters, because the observable signal is *success*. Note the asymmetry that makes the choice easy. The cost of failing closed is paid immediately, visibly, by the person who can fix it. The cost of failing open is paid later, invisibly, by everyone. ## Why the guarantee is hard to hold in practice The invariant is not a property of one config file; it is a property of **every resolver in the organisation**. Typical leaks: - A developer machine that still has the default public source configured alongside the feed, because it was set up before the policy existed. - A container base image that bakes in a public source, so anything built on it inherits the fallback. - A nested or vendored build — a tool that shells out to its own package manager with its own configuration. - A build tool whose *default* is to add a public source unless explicitly told not to, so a fresh project starts non-compliant. - A per-team override added during an incident to unblock a release and never removed. Each of these is individually reasonable and collectively fatal, because the guarantee is only as strong as the weakest resolver that ships to production. ## Prove it; do not assume it The reliable check is a **canary name**: reserve a name in your namespace that will never be published, have a scheduled job in CI attempt to resolve it, and assert that resolution fails. If it ever succeeds — from any source, with any contents — the invariant is broken and you know within a day rather than after an incident. Complementary signals worth wiring up: - Emit the resolved source for every dependency from CI and alert on anything that did not come from the feed. - Watch egress from build runners. A runner connecting directly to a public index is evidence the feed is not the only source, whatever the configuration says. - Compare the set of names appearing in built artifacts against the feed's inventory; anything present in a shipped image but absent from the feed came from somewhere. ## What breaks when you close the door Closing it turns a class of silent risk into a steady stream of ordinary friction: an engineer wants a genuinely useful third-party package, the feed has never held it, and the build fails. That is correct behaviour, and it is also the moment the whole scheme lives or dies. If the request path from *my build failed* to *the package is available* is measured in hours, people use it. If it is measured in days, they will vendor the code, copy files into the repository, or re-add a public source, and you have converted a controlled gap into an uncontrolled one while believing you closed it. So the fail-closed rule has a partner obligation: a fast, obvious, low-ceremony intake path for the legitimate miss. The gate is the easy half. Making the door people are supposed to use faster than the wall they would climb over is the hard half. ## The phrasing to avoid in an interview "We configured our internal repository first, so ours wins." Priority ordering is a preference, not a guarantee: it still leaves the outward path open for anything the first source happens not to have at that moment. The strong statement is that there is no outward path for your namespace at all.
- How do you prove the property holds across hundreds of repositories?Test it rather than audit configuration. Reserve a name in your namespace that is never published, have a scheduled CI job try to resolve it, and alert if it ever succeeds. Back that with telemetry on where dependencies actually resolved from and on egress from build runners to public indexes, so you catch the machine whose config nobody remembered to change.
- Ordering the internal source first is not enough. Why not?Ordering is a preference, not a boundary. It only decides which source is consulted first; anything the first source does not have at that instant still continues outward, and transient gaps, cache misses and new names all produce that instant. The guarantee you want is that names in your namespace have no outward path at all, so a miss ends in an error rather than in a lookup somewhere else.
- What is the operational cost of removing public fallback, and how do you absorb it?Every first use of a legitimate third-party package now fails the build. That is correct but expensive, so the intake path has to be fast and obvious — a request that resolves in hours, ideally self-service for low-risk cases. If it takes days, teams vendor the code or re-add a public source, and you have swapped a visible gap for an invisible one.
- A developer's laptop still has a public source configured. How bad is that?Bad enough to treat as a live finding, though not as urgent as a CI runner. It can pull an unexpected artifact into a lockfile or local cache that then travels into a pull request, and it means the person's local build no longer reflects the sanctioned dependency set. Fix it by shipping the resolver configuration as managed machine state rather than as documentation.
A door that jams shut on an unrecognised key is safer than one that quietly swings open to the street.
saying these in an interview costs you the question
- Says listing the internal source first is enough
- Treats a failed resolution as a bug to work around
- Checks configuration files instead of testing the behaviour
- Secures CI but ignores laptops and base images
- Closes the door without a fast intake path