Sign In

atteguard

Package Overview
Dependencies
Maintainers
1
Versions
2
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

atteguard

Shared trust-decision primitives (path containment, secret scanning, argv allowlisting, authority-claim conformance, signature verification, diagnostic-event validation) for the forest tool ecosystem.

latest
Source
npmnpm
Version
0.2.1
Version published
Weekly downloads
41
-86.94%
Maintainers
1
Weekly downloads
 
Created
Source

Atteguard

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.

Install

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.

Quick Start for External Engineering Teams

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";

Release Policy

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).

Modules

ModuleStatusProvides
atteguard/path-safetyshippedSymlink-escape-resistant path containment checks
atteguard/text-safetyshippedSecret/path/token pattern scanning and redaction
atteguard/command-safetyshippedArgv allowlisting, git-ref validation
atteguard/signingshippedSignature verification requiring a caller-supplied trusted key
atteguard/authorityshippedAuthority-claims registry + call-site conformance checker
atteguard/telemetryshippedDiagnostic-event schema validation and policy checking
atteguard/git-safetyshippedSafe in-process git execution with ref assertion
atteguard/cli-safetyshippedCLI flag contract validation and help rendering

path-safety

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.

text-safety

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.

command-safety

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.

git-safety

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.

cli-safety

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.

signing

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.

authority

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.

telemetry

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.

Development

npm test

Keywords

security

FAQs

Package last updated on 28 Jul 2026

Did you know?

Socket

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.

Install

Related posts