
Company News
Free Business Plan Upgrades for Open Source Maintainers
Open source maintainers are under more pressure than ever. We're raising our open source program from the Team plan to the Business plan, free.
Shared trust-decision primitives (path containment, secret scanning, argv allowlisting, authority-claim conformance, signature verification, diagnostic-event validation) for the forest tool ecosystem.
Atteguard is a shared library of trust-decision primitives for the forest tool ecosystem. It exists because an adversarial security review across the ecosystem found the same bug shape repeating across many independently maintained repos: a real, well-built verification mechanism exists somewhere in a given tool, but a second, equivalent-effect path into the same trust decision either wasn't wired to it, or trusted caller-supplied data at face value. Atteguard extracts the best existing implementation of each recurring primitive into one place, so it gets fixed once instead of separately reinvented (and separately re-broken) per repo.
Atteguard ships no CLI. It is a library other tools import from.
npm install atteguard
Not yet published to the public npm registry — this section describes the intended install path once the first tagged release ships (tracked in atteguard#7). Until then, consumers cannot depend on Atteguard from outside this workspace.
Atteguard modules can be imported individually into any Node.js project:
// 1. Path Safety (Symlink & Traversal Prevention)
import { isPathInside, resolveContainedPath, resolveContainedFilePath } from "atteguard/path-safety";
// 2. Text Safety (Secret Redaction & Log Sanitization)
import { redactSensitiveTextForDisplay, redactSensitiveTextForSecurity, findSensitiveContent } from "atteguard/text-safety";
// 3. Safe Git Subprocess Execution
import { gitRevParseSafe, gitAheadBehindSafe, assertCleanWorktree } from "atteguard/git-safety";
// 4. CLI Contract Enforcement & Help Rendering
import { validateCliFlags, renderCliHelp } from "atteguard/cli-safety";
Atteguard's canonical public identity is the atteguard package on npm,
sourced from github.com/attebury/atteguard, MIT-licensed. Versioning is
strict SemVer; the project is pre-1.0, so breaking changes are possible on a
minor version bump until 1.0.0. Because these are trust-decision primitives —
a wrong or stale version silently reintroduces the exact bug class Atteguard
exists to close — consumers should pin an exact version, not a range, and
verify the installed tarball's integrity via their own lockfile rather than
trusting an unpinned resolution. A release is only canonical once it has a
Releasepress-backed receipt: an exact registry version, tarball integrity,
source commit, and installed-package smoke evidence (every documented
subpath export importable and callable from a fresh install outside this
repo — see scripts/smoke-check-installed-package.mjs).
| Module | Status | Provides |
|---|---|---|
atteguard/path-safety | shipped | Symlink-escape-resistant path containment checks |
atteguard/text-safety | shipped | Secret/path/token pattern scanning and redaction |
atteguard/command-safety | shipped | Argv allowlisting, git-ref validation |
atteguard/signing | shipped | Signature verification requiring a caller-supplied trusted key |
atteguard/authority | shipped | Authority-claims registry + call-site conformance checker |
atteguard/telemetry | shipped | Diagnostic-event schema validation and policy checking |
atteguard/git-safety | shipped | Safe in-process git execution with ref assertion |
atteguard/cli-safety | shipped | CLI flag contract validation and help rendering |
import { resolveCanonicalRoot, resolveContainedPath, resolveContainedFilePath, isPathInside } from "atteguard/path-safety";
const canonicalRoot = resolveCanonicalRoot(trustedRoot);
const { canonical_path } = resolveContainedPath({
name: "example",
candidatePath: someUntrustedPath,
root: trustedRoot,
canonicalRoot
});
resolveContainedPath runs a two-phase directory containment check: a lexical check
against the root before resolving symlinks, then a canonical check — after
resolving symlinks on both sides — against the root's own canonical form,
not the candidate's. Checking a canonicalized path for containment within
itself is always trivially true, which is what makes that shortcut unsafe:
a symlink inside the root that points outside it will pass a naive
self-referential check. resolveContainedPath throws an Error with
.code === "unsafe_destination_path" and a .reason field identifying the
specific failure (path_lexical_escape, path_symlink_escape,
candidate_path_missing, candidate_path_not_directory,
candidate_path_unreadable) when containment fails.
resolveContainedFilePath applies the same two-phase contract for regular
files and rejects symlinked file inputs explicitly. isPathInside remains
a lexical helper only; it does not resolve symlinks and should not be used
as the security boundary when untrusted paths may traverse symlinked
directories or files.
import { findSensitiveText, findSensitiveContent, redactSensitiveTextForDisplay, redactSensitiveTextForSecurity, isSensitiveValueKey, shouldStripKey } from "atteguard/text-safety";
findSensitiveText("postgres://user : pass @ host/db"); // [{ code: "credential_url", label: "credential URL" }]
redactSensitiveTextForDisplay("token: <example-token-value>"); // "token: [redacted]"
findSensitiveText/findSensitiveContent detect local paths, credential
URLs, JWT/AWS-key/GitHub-Slack-Stripe-style tokens, bearer tokens, private
key material, env-style assignments, and key-name-based sensitive fields
(password, api_key, secret, etc.) — for callers that want to reject
content outright. findSensitiveContent is cycle-aware and depth-bounded
so hostile object graphs fail closed with findings instead of exhausting the
stack. redactSensitiveTextForDisplay masks matched spans in place for UI
or logs. It is not a security gate: display redaction may intentionally
truncate oversized input. Use findSensitiveText/findSensitiveContent to
reject unsafe content, or redactSensitiveTextForSecurity when truncation
must fail closed.
import { validateAllowlistedCommand, defineAllowlistedCommand, integerArg, assertGitRef } from "atteguard/command-safety";
const definitions = {
"example.status": defineAllowlistedCommand("example", "status", ["example", "status", "--json"]),
"example.pr_view": defineAllowlistedCommand("example", "pr_view", ["example", "pr", "view", "--number", integerArg(), "--json"]),
};
validateAllowlistedCommand({ source: "example", command_id: "status", argv: ["example", "status", "--json"] }, { definitions });
assertGitRef("--show-toplevel"); // throws — flag-injection-shaped ref
validateAllowlistedCommand only accepts argv that exactly matches a
pre-declared definition (literal tokens matched verbatim, typed placeholder
slots like integerArg()/identifierArg() matched by pattern) — anything
not matching an existing definition is rejected, not merely warned about.
It also rejects raw command strings, shell metacharacters, declared
environment variables, mutation flags, uncontained cwd values, and provider-boundary fields
(curl, api_endpoint, etc.) by default. An optional extraValidator
hook lets a consumer plug in its own domain-specific shape checks without
atteguard needing to know about them.
assertGitRef/assertGitRemote/assertCrOpenBranchRef are narrower,
git-specific companions: safe-shape checks for ref/remote strings before
they reach a git subprocess argv or a forge API call.
import { gitRevParseSafe, gitAheadBehindSafe, gitCurrentBranchSafe, assertCleanWorktree } from "atteguard/git-safety";
const sha = gitRevParseSafe(cwd, "HEAD");
const { ahead_by, behind_by } = gitAheadBehindSafe(cwd, "origin/main", "HEAD");
const currentBranch = gitCurrentBranchSafe(cwd);
assertCleanWorktree(cwd);
Provides safe, in-process local git execution wrappers with strict ref validation
and subprocess flag protection (assertGitRef). Core helpers throw coded
errors when git is unavailable or rejects the request, so callers do not
have to infer failure cause from null.
import { validateCliFlags, renderCliHelp, formatCliError } from "atteguard/cli-safety";
const { ok, errors } = validateCliFlags({
flags: { json: true, bad_flag: true },
contract: { allowed_flags: ["json", "help"] }
});
const helpText = renderCliHelp({ group: "issue", subcommand: "open", catalog });
const errorOutput = formatCliError({ error: new Error("Invalid argument"), asJson: true });
Provides standardized CLI flag contract validation (failing closed on unrecognized
flags), --help rendering from a command catalog, and formatted CLI error objects.
import { signPayload, verifyPayloadSignature } from "atteguard/signing";
const signed = signPayload({ payloadPath, payloadType: "example.payload", signerId: "example_signer", privateKeyPem });
const verified = verifyPayloadSignature({ signaturePacket: signed, payloadPath, expectedPublicKeyPem: trustedKeyFromYourOwnConfig });
expectedPublicKeyPem is required, with no fallback. verifyPayloadSignature
never derives the key it verifies against from signaturePacket itself —
only from the caller-supplied expectedPublicKeyPem, sourced from the
caller's own trust store/config. The packet's signer.public_key field is
carried for portability/debugging only and never feeds the cryptographic
check. Without that separation, a signature is only proof "some keypair
signed this," not proof of whose — anyone can generate a keypair, sign
whatever they want, and embed their own public key in the packet as if it
were authoritative.
import { checkAuthorityClaims } from "atteguard/authority";
const report = checkAuthorityClaims({ root: process.cwd() });
Ported from skillpress's authority-claims.json + check-authority-claims.mjs
pilot (skillpress issues #57–59), including the v2 fix for skillpress issue
#61. A repo declares an authority-claims.json registry: claims[] (a guard
function, e.g. a path-containment check, and which writer files it's
supposed to apply to), write_sites[] (the specific function in a
specific file making a specific governed fs write call, and which
claims that site is required to satisfy), and optional
non_publication_sites[] (a write call that's deliberately exempt, with a
bounded reason code).
checkAuthorityClaims parses every governed source file's AST (not text
search — it fails closed on evasions like aliasing the fs namespace,
destructuring a write function out of it, importing it by name directly, or
using unsupported bare fs imports) and cross-references three things against each other: declared
claims against their guard functions actually being imported and called at
each writer, declared write sites against what the AST actually finds at
that writer+function+call triple, and declared claims against declared
write sites. The write-site cross-reference is what makes this call-site-
level rather than file-level: a write site the registry declares but the
AST can't find is authority_claims_write_site_missing (registry drift);
a write call the AST finds but the registry never declared is
authority_claims_write_site_unregistered — so adding a new write call
to an already-covered file doesn't silently pass just because that file
already has a legitimately guarded call elsewhere in it.
Governed writes include sync node:fs writes, callback-style async
node:fs writes, and exact node:fs/promises namespace/default writes.
Guard calls are checked against bindings rather than raw identifier names,
so a shadowed local function cannot satisfy an authority claim by spelling.
The report's own authority field is "bounded_conformance_only", and it
explicitly disclaims arbitrary_call_graph_proof — passing confirms the
registry and the source agree, not that every possible code path is safe.
import { validateDiagnosticEvent, NEUTRAL_DIAGNOSTIC_EVENT_TYPE, buildRoutingDiagnosticEvent } from "atteguard/telemetry";
validateDiagnosticEvent(event, { schema: NEUTRAL_DIAGNOSTIC_EVENT_TYPE }); // simpler routing/collector event shape
Ported from wayfinder's own telemetry validator: a vendor-neutral
diagnostic-event schema (diagnostic.event.v1) with no product-specific
naming or fields, suitable for any consumer. Every string field is scanned
through atteguard/text-safety's findSensitiveText, so consumers get the
same secret/path detection as every other module here instead of an
independently-drifting copy. buildRoutingDiagnosticEvent is also
available for building a bounded event from a failed routing/collector
command. The shared validator follows the Waylane fork's neutral
diagnostic.event.v1 shape, including strict event-field allowlists,
privacy flag enums, dedupe strategies, and bounded not_authority_for
authority disclaimers.
A richer, product-specific schema (atteware.diagnostic_event.v1, with an
impact field and sink/policy validation) previously lived here but has
been removed — that naming and shape is product-specific and doesn't
belong in a vendor-neutral shared package. Consumers that need it (e.g.
atteway) own that schema locally now, reusing only findSensitiveText from
this module for the shared secret-scanning behavior.
npm test
FAQs
Shared trust-decision primitives (path containment, secret scanning, argv allowlisting, authority-claim conformance, signature verification, diagnostic-event validation) for the forest tool ecosystem.
The npm package atteguard receives a total of 27 weekly downloads. As such, atteguard popularity was classified as not popular.
We found that atteguard demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Company News
Open source maintainers are under more pressure than ever. We're raising our open source program from the Team plan to the Business plan, free.

Security News
The supply chain control that delays freshly published gems now covers lockfile generation and gem vendoring in Ruby projects.

Security News
During a UK cyber test, a Mythos 5 agent used sockpuppets, social engineering, and prompt injection to try to get a maintainer to merge malware.