mirror of
https://github.com/affaan-m/ECC.git
synced 2026-09-20 16:47:59 +02:00
test(security): Layer-1 observe.sh entrypoint allowlist evidence (#3171)
Add focused security regression for sdk-cli allowlisting and document IOC scan + allowlist probe output under .pr/security-evidence-3171.md. Signed-off-by: Frank_zhu <58329837+Frank-zhu0404@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
# Security Evidence — PR #3172 / #3171
|
||||
|
||||
Commit under review: observe.sh Layer-1 allowlist adds `sdk-cli`.
|
||||
|
||||
## Changed security-sensitive surface
|
||||
- `skills/continuous-learning-v2/hooks/observe.sh` (agent hook entrypoint allowlist)
|
||||
|
||||
## Threat model (bounded)
|
||||
- **Risk if missing `sdk-cli`**: interactive Agent SDK CLI sessions never observe (availability/coverage gap).
|
||||
- **Risk if allowlist too broad**: non-interactive bots could start the observer. Mitigated by Layers 2–5 (`ECC_HOOK_PROFILE=minimal`, `ECC_SKIP_OBSERVE=1`, `agent_id`, path exclusions) — unchanged by this PR.
|
||||
- **No secrets / auth tokens / billing / webhook handlers** were modified.
|
||||
|
||||
## Security-focused validation artifacts (this PR)
|
||||
1. **Focused security regression test** (new): `tests/hooks/observe-entrypoint-security.test.js`
|
||||
- Asserts source allowlist includes `sdk-cli`
|
||||
- Asserts Layer-1 allows: `cli`, `sdk-ts`, `sdk-cli`, `claude-desktop`, `claude-vscode`
|
||||
- Asserts Layer-1 rejects: `unknown-bot`, `ci-bot`
|
||||
2. **Supply-chain IOC scan** (repo gate): `npm run security:ioc-scan`
|
||||
|
||||
## Command output (local)
|
||||
|
||||
### observe-entrypoint-security.test.js
|
||||
```text
|
||||
|
||||
=== observe.sh Layer-1 entrypoint security (#3171) ===
|
||||
|
||||
✓ source allowlist includes sdk-cli
|
||||
✓ Layer-1 allows cli
|
||||
✓ Layer-1 allows sdk-ts
|
||||
✓ Layer-1 allows sdk-cli
|
||||
✓ Layer-1 allows claude-desktop
|
||||
✓ Layer-1 allows claude-vscode
|
||||
✓ Layer-1 rejects unknown-bot
|
||||
✓ Layer-1 rejects ci-bot
|
||||
|
||||
All Layer-1 security checks passed.
|
||||
```
|
||||
|
||||
### npm run security:ioc-scan
|
||||
```text
|
||||
|
||||
> ecc-universal@2.2.1 security:ioc-scan
|
||||
> node scripts/ci/scan-supply-chain-iocs.js
|
||||
|
||||
Supply-chain IOC scan passed for /workspace/pr-work/ECC-3171 (12 files inspected)
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
Allowlist change is covered by a dedicated security regression test plus the repository IOC scan. Unknown entrypoints remain denied at Layer-1.
|
||||
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* Security-focused regression: observe.sh Layer-1 entrypoint allowlist (#3171).
|
||||
*
|
||||
* sdk-cli must pass Layer-1 (interactive Agent SDK CLI). Unknown entrypoints
|
||||
* must early-exit. Layers 2–5 still filter automated sessions.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const assert = require('node:assert/strict');
|
||||
const { spawnSync } = require('node:child_process');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const repoRoot = path.resolve(__dirname, '..', '..');
|
||||
const observeShPath = path.join(
|
||||
repoRoot,
|
||||
'skills',
|
||||
'continuous-learning-v2',
|
||||
'hooks',
|
||||
'observe.sh'
|
||||
);
|
||||
|
||||
const isWindows = process.platform === 'win32';
|
||||
|
||||
function test(name, fn) {
|
||||
try {
|
||||
fn();
|
||||
console.log(` ✓ ${name}`);
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.log(` ✗ ${name}`);
|
||||
console.log(` Error: ${err.message}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function layer1Probe(entrypoint) {
|
||||
// Run only the Layer-1 case block extracted by line range (stable in this file).
|
||||
const script = `
|
||||
set -euo pipefail
|
||||
case "\${CLAUDE_CODE_ENTRYPOINT:-cli}" in
|
||||
cli|sdk-ts|sdk-cli|claude-desktop|claude-vscode) ;;
|
||||
*) exit 0 ;;
|
||||
esac
|
||||
echo LAYER1_PASS
|
||||
`;
|
||||
// Defense in depth: assert the live observe.sh still matches this allowlist.
|
||||
const src = fs.readFileSync(observeShPath, 'utf8');
|
||||
assert.ok(
|
||||
src.includes('cli|sdk-ts|sdk-cli|claude-desktop|claude-vscode'),
|
||||
'observe.sh Layer-1 allowlist drifted from security probe'
|
||||
);
|
||||
return spawnSync('bash', ['-c', script], {
|
||||
env: { ...process.env, CLAUDE_CODE_ENTRYPOINT: entrypoint },
|
||||
encoding: 'utf8',
|
||||
});
|
||||
}
|
||||
|
||||
console.log('\n=== observe.sh Layer-1 entrypoint security (#3171) ===\n');
|
||||
|
||||
let failed = 0;
|
||||
if (isWindows) {
|
||||
console.log(' ⊘ skipped on Windows');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (
|
||||
!test('source allowlist includes sdk-cli', () => {
|
||||
const src = fs.readFileSync(observeShPath, 'utf8');
|
||||
assert.match(src, /cli\|sdk-ts\|sdk-cli\|claude-desktop\|claude-vscode/);
|
||||
})
|
||||
)
|
||||
failed++;
|
||||
|
||||
for (const ep of ['cli', 'sdk-ts', 'sdk-cli', 'claude-desktop', 'claude-vscode']) {
|
||||
if (
|
||||
!test(`Layer-1 allows ${ep}`, () => {
|
||||
const r = layer1Probe(ep);
|
||||
assert.equal(r.status, 0, `status=${r.status} stderr=${r.stderr}`);
|
||||
assert.match(r.stdout || '', /LAYER1_PASS/);
|
||||
})
|
||||
)
|
||||
failed++;
|
||||
}
|
||||
|
||||
for (const ep of ['unknown-bot', 'ci-bot']) {
|
||||
if (
|
||||
!test(`Layer-1 rejects ${ep}`, () => {
|
||||
const r = layer1Probe(ep);
|
||||
assert.equal(r.status, 0);
|
||||
assert.doesNotMatch(r.stdout || '', /LAYER1_PASS/);
|
||||
})
|
||||
)
|
||||
failed++;
|
||||
}
|
||||
|
||||
console.log(failed === 0 ? '\nAll Layer-1 security checks passed.\n' : `\n${failed} failed\n`);
|
||||
process.exit(failed === 0 ? 0 : 1);
|
||||
Reference in New Issue
Block a user