Skip to content

fix(security): harden high scan findings#258

Merged
davida-ps merged 3 commits into
mainfrom
davida-ps/fix-high-security-findings
Jun 7, 2026
Merged

fix(security): harden high scan findings#258
davida-ps merged 3 commits into
mainfrom
davida-ps/fix-high-security-findings

Conversation

@davida-ps
Copy link
Copy Markdown
Collaborator

@davida-ps davida-ps commented Jun 7, 2026

User description

Summary

  • Fixes the 4 High findings from the Claude Security scan.
  • Adds fail-closed comparator range matching for NanoClaw advisory checks.
  • Validates integrity IPC request IDs and contains result writes inside the intended host results directory.
  • Treats explicit malicious ClawHub/VirusTotal verdicts as blocking reputation signals.
  • Replaces DAST target hook execution with static hook source inspection so scanner runs no longer import, transpile, or invoke untrusted handler code.

Security benefit

  • Prevents vulnerable range-based advisories from being silently dropped.
  • Blocks IPC request ID path traversal into arbitrary host JSON writes.
  • Prevents malicious registry verdicts from passing based on a high numeric score.
  • Removes the scanner RCE path caused by executing hook handlers found in the scan target.

Testing

  • node skills/clawsec-nanoclaw/test/security-hardening.test.mjs
  • node skills/clawsec-clawhub-checker/test/reputation_check.test.mjs
  • node skills/clawsec-scanner/test/dast_harness.test.mjs
  • node skills/clawsec-scanner/test/reviewer_regressions.test.mjs
  • python3 utils/validate_skill.py skills/clawsec-nanoclaw
  • python3 utils/validate_skill.py skills/clawsec-clawhub-checker
  • python3 utils/validate_skill.py skills/clawsec-scanner (passes with existing README recommendation warning)
  • /opt/homebrew/bin/npx tsc --noEmit
  • PATH="/opt/homebrew/bin:$PATH" /opt/homebrew/bin/npx eslint . --ext .ts,.tsx,.js,.jsx,.mjs --max-warnings 0 --ignore-pattern '.worktrees/**'
  • PATH="/opt/homebrew/bin:$PATH" /opt/homebrew/bin/npm run build
  • node skills/clawsec-scanner/scripts/dast_runner.mjs --target skills/clawsec-scanner/hooks --format json
  • git diff --check

Note: the literal repo lint command without the .worktrees/** ignore fails locally because this checkout contains an ignored .worktrees/pr-255/dist artifact with bundled JS output; source lint passes with that local artifact excluded.


Generated description

Below is a concise technical summary of the changes proposed in this PR:
Harden the ClawHub reputation gate, NanoClaw integrity service, and NanoClaw advisory matching by blocking explicit malicious verdicts, validating integrity IPC request IDs, and adding comparator-range semantics before persisting results. Replace the scanner’s DAST execution harness with a static inspection flow that only reads handler sources for coverage and risk signals so untrusted hook code is never imported or executed.

TopicDetails
ClawHub reputation Harden the ClawHub reputation gate by honoring explicit malicious verdicts (blockOnMaliciousScannerData), mock-testing the behavior, and updating the skill metadata/CHANGELOG to reflect the new 0.0.5 release.
Modified files (5)
  • skills/clawsec-clawhub-checker/CHANGELOG.md
  • skills/clawsec-clawhub-checker/SKILL.md
  • skills/clawsec-clawhub-checker/scripts/check_clawhub_reputation.mjs
  • skills/clawsec-clawhub-checker/skill.json
  • skills/clawsec-clawhub-checker/test/reputation_check.test.mjs
Latest Contributors(2)
UserCommitDate
David.a@prompt.securityfix(security): harden ...June 07, 2026
david@abutbul.comfix(release): exclude ...May 14, 2026
NanoClaw safety Strengthen NanoClaw advisory handling and IPC persistence by adding semantic comparator parsing plus fail-closed tests, validating/normalizing IPC request IDs (validateRequestId/resolveResultPath), and bumping the skill metadata to 0.0.7.
Modified files (6)
  • skills/clawsec-nanoclaw/CHANGELOG.md
  • skills/clawsec-nanoclaw/SKILL.md
  • skills/clawsec-nanoclaw/host-services/integrity-handler.ts
  • skills/clawsec-nanoclaw/lib/advisories.ts
  • skills/clawsec-nanoclaw/skill.json
  • skills/clawsec-nanoclaw/test/security-hardening.test.mjs
Latest Contributors(2)
UserCommitDate
David.a@prompt.securityfix(security): harden ...June 07, 2026
david.a@prompt.securityfeat(advisories): add ...May 24, 2026
Static DAST Convert the DAST flow to static source inspection: read hook files via readHookSource, detect exports, collect risk signals, reuse that output across events, and log coverage-only findings while documenting the new behavior and testing that hooks are not executed.
Modified files (7)
  • skills/clawsec-scanner/CHANGELOG.md
  • skills/clawsec-scanner/SKILL.md
  • skills/clawsec-scanner/hooks/clawsec-scanner-hook/handler.ts
  • skills/clawsec-scanner/scripts/dast_hook_executor.mjs
  • skills/clawsec-scanner/scripts/dast_runner.mjs
  • skills/clawsec-scanner/skill.json
  • skills/clawsec-scanner/test/dast_harness.test.mjs
Latest Contributors(2)
UserCommitDate
David.a@prompt.securityfix(security): harden ...June 07, 2026
david@abutbul.comfix(release): exclude ...May 14, 2026
Review this PR on Baz | Customize your next review

Comment on lines 122 to 133
const comparatorTokens = spec
.split(/[\s,]+/)
.map((token) => token.trim())
.filter(Boolean);

if (
comparatorTokens.length > 0 &&
comparatorTokens.every((token) => /^(?:<=|>=|<|>|=)\s*v?\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(token))
) {
return comparatorTokens.every((token) => evaluateComparator(token));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

versionMatches should keep comparator+version pairs together, otherwise spaced ranges like <= 0.0.2 fall through to true and match every version — can we split only on commas or extract comparator tokens with a regex?

Severity

Want Baz to fix this for you? Activate Fixer

Other fix methods

Fix in Cursor

Prompt for AI Agents
Before applying, verify this suggestion against the current code. In
skills/clawsec-nanoclaw/lib/advisories.ts around lines 78-163, specifically in
versionMatches where you build comparatorTokens (lines 122-133) and evaluateComparator
(lines 103-117), the tokenizer splits on whitespace so a comparator like "<= 0.0.2"
becomes separate tokens ("<=" and "0.0.2"), causing the every() validation to fail and
the function to incorrectly fall back to returning true. Refactor comparator parsing to
extract full operator+version pairs with a regex (e.g., find all occurrences of optional
operator followed immediately by a semver version, allowing whitespace between operator
and version) instead of spec.split(/[ \s,]+/). After token extraction, evaluate each
comparator token; if the spec contains comparator operators but cannot be tokenized into
valid pairs, return false rather than true so invalid/partially parsed ranges don’t
match everything.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commit 8157697 addressed this comment by replacing the whitespace split with regex-based comparator extraction that keeps operator+version pairs together, including spaced forms like <= 0.0.2. It also returns false when comparator operators are present but cannot be parsed into valid pairs, preventing the прежний fallback-to-true behavior.

Comment on lines +472 to +483
/**
* @param {HookDescriptor} hook
* @param {string} targetPath
* @param {string} _targetPath
* @param {number} timeoutMs
* @returns {Promise<Vulnerability[]>}
*/
async function evaluateHook(hook, targetPath, timeoutMs) {
async function evaluateHook(hook, _targetPath, timeoutMs) {
const findings = [];
const invocationTimeoutMs = Math.max(1000, timeoutMs);

for (const eventKey of hook.events) {
const safeEvent = buildEvent(eventKey, "safe baseline input", targetPath);
const safeContext = {
skillPath: hook.hookDir,
agentPlatform: "openclaw",
dastMode: true,
targetPath: path.resolve(targetPath),
event: eventKey,
};

const safeResult = await invokeHookHarness(hook, safeEvent, safeContext, invocationTimeoutMs);
const inspection = await inspectHookHandler(hook, invocationTimeoutMs);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we move const inspection = await inspectHookHandler(...) outside the for (const eventKey of hook.events) loop and reuse it per hook? It only depends on handlerPath/exportName, so this repeats the same spawn/read/analysis for every event.

Severity

Want Baz to fix this for you? Activate Fixer

Other fix methods

Fix in Cursor

Prompt for AI Agents
Before applying, verify this suggestion against the current code. In
`skills/clawsec-scanner/scripts/dast_runner.mjs` around lines 472-517 inside
`evaluateHook`, the code currently calls `await inspectHookHandler(hook,
invocationTimeoutMs)` once per `eventKey`, even though `inspectHookHandler` only depends
on the hook descriptor and returns the same inspection result for each event. Refactor
by running `inspectHookHandler` a single time before the `for (const eventKey of
hook.events)` loop, storing the result (including `timedOut`, `parseError`, `stderr`,
and `parsed`), and then reusing it inside the loop to generate the same per-event
findings/IDs/messages. If any part of `inspectHookHandler`’s output is actually
event-specific, adjust `inspectHookHandler` to accept `eventKey` (or document/verify the
invariant) before moving it; otherwise ensure the change preserves the existing
vulnerability text while eliminating redundant spawns.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commit 8157697 addressed this comment by moving inspectHookHandler(hook, invocationTimeoutMs) before the for (const eventKey of hook.events) loop and reusing the single inspection result for every event. The per-event findings still get generated, but the redundant spawn/read/analysis work now happens only once per hook.

Comment on lines +89 to +96
const semverPattern = String.raw`v?\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?`;

const parseVersion = (ver: string): number[] | null => {
const match = ver.match(new RegExp(`^${semverPattern}$`));
if (!match) return null;
const parts = match[0].replace(/^v/, '').match(/^(\d+)\.(\d+)\.(\d+)/);
if (!parts) return null;
return [parseInt(parts[1], 10), parseInt(parts[2], 10), parseInt(parts[3], 10)];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parseVersion doesn't guard against a null match before accessing match[0], and also strips prerelease data so versionMatches() treats 1.2.3-beta.1 as equal to 1.2.3 — should we add a null guard (returning null for malformed input) and preserve prerelease segments while only stripping build metadata after +?

Severity web_search

Want Baz to fix this for you? Activate Fixer

Other fix methods

Fix in Cursor

Prompt for AI Agents
In skills/clawsec-nanoclaw/lib/advisories.ts around lines 89-96, refactor
`parseVersion()` to: 1. Add a null guard after `ver.match(...)` — if `match` is falsy,
return `null` immediately instead of throwing on `match[0]` access. Ensure all
downstream callers of `parseVersion()` handle a possible `null` return gracefully. 2.
Return both the numeric components and the prerelease segment (strip only build metadata
after `+`), then update the comparator/equality logic (`=`, `>=`, `<=`, and any
caret/tilde paths) to compare prereleases using SemVer rules (prerelease < release;
prerelease identifiers compared lexically/numerically per spec), so prereleases no
longer collapse into the same match as the corresponding release.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commit a0c50e4 addressed this comment by adding a null check in parseVersion() and returning null for non-matching inputs instead of touching match[0]. It also preserves prerelease identifiers, strips only build metadata, and updates version comparison/range logic so prereleases no longer collapse into the base release.

Comment on lines +107 to +111
const evaluateComparator = (comparator: string): boolean => {
const match = comparator.trim().match(new RegExp(`^(<=|>=|<|>|=)?\\s*(${semverPattern})$`));
if (!match) return false;

const operator = match[1] || '=';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new comparator evaluation block duplicates the semver parsing/comparison already in skills/hermes-attestation-guardian/lib/semver.mjs and skills/clawsec-suite/hooks/clawsec-advisory-guardian/lib/version.mjs, should we reuse the shared helper instead of copying this logic here?

Severity

Want Baz to fix this for you? Activate Fixer

Comment on lines +96 to +99
const semverPattern = String.raw`v?\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?`;
const semverRegex = new RegExp(
String.raw`^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$`
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

semverPattern is unused here, so with noUnusedLocals this may fail the TypeScript build — can we drop it and keep only semverRegex, or wire it in if needed?

Severity

Want Baz to fix this for you? Activate Fixer

Other fix methods

Fix in Cursor

Prompt for AI Agents
Before applying, verify this suggestion against the current code. In
skills/clawsec-nanoclaw/lib/advisories.ts around lines 96-99 inside versionMatches,
remove the unused semverPattern declaration (it’s defined but never referenced).
Ensure only semverRegex remains (or, if you prefer, rewrite semverRegex to use
semverPattern) so the helper compiles cleanly under noUnusedLocals/noUnusedParameters.

Comment on lines 218 to 222
// Tilde range (~1.2.3): patch-level compatibility (1.2.x where x >= 3)
if (spec.startsWith('~')) {
if (vParts[0] !== specParts[0]) return false;
if (vParts[1] !== specParts[1]) return false;
return vParts[2] >= specParts[2];
const upperBound = { major: specParts.major, minor: specParts.minor + 1, patch: 0, prerelease: [] };
return compareVersions(vParts, specParts) >= 0 && compareVersions(vParts, upperBound) < 0;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new versionMatches duplicates the caret/tilde/prerelease semver logic already in other packages, so we now have three copies to keep in sync — should we extract a shared helper like skills/shared/semver.ts and import it here?

Severity

Want Baz to fix this for you? Activate Fixer

@davida-ps davida-ps merged commit 3cef7aa into main Jun 7, 2026
19 checks passed
@davida-ps davida-ps deleted the davida-ps/fix-high-security-findings branch June 7, 2026 10:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant